Merge lp:~urbanape/ubuntu-sso-client/initial-darwin-port into lp:ubuntu-sso-client

Proposed by Zachery Bir
Status: Rejected
Rejected by: Manuel de la Peña
Proposed branch: lp:~urbanape/ubuntu-sso-client/initial-darwin-port
Merge into: lp:ubuntu-sso-client
Prerequisite: lp:~mandel/ubuntu-sso-client/fix-webclient-tests
Diff against target: 1544 lines (+766/-429)
23 files modified
data/qt/darwin.qss (+5/-0)
data/qt/resources.qrc (+1/-0)
run-tests (+1/-1)
ubuntu_sso/keyring/__init__.py (+2/-6)
ubuntu_sso/keyring/linux.py (+0/-6)
ubuntu_sso/keyring/pykeyring.py (+2/-3)
ubuntu_sso/keyring/tests/test_pykeyring.py (+8/-9)
ubuntu_sso/main/__init__.py (+27/-1)
ubuntu_sso/main/darwin.py (+110/-0)
ubuntu_sso/main/perspective_broker.py (+352/-0)
ubuntu_sso/main/tests/test_darwin.py (+43/-0)
ubuntu_sso/main/tests/test_windows.py (+3/-1)
ubuntu_sso/main/windows.py (+23/-335)
ubuntu_sso/networkstate/__init__.py (+11/-11)
ubuntu_sso/networkstate/darwin.py (+95/-0)
ubuntu_sso/networkstate/linux.py (+7/-39)
ubuntu_sso/networkstate/networkstates.py (+51/-0)
ubuntu_sso/networkstate/tests/test_linux.py (+9/-10)
ubuntu_sso/qt/__init__.py (+7/-0)
ubuntu_sso/qt/main/__init__.py (+1/-1)
ubuntu_sso/utils/__init__.py (+6/-2)
ubuntu_sso/utils/qtwisted.py (+1/-3)
ubuntu_sso/utils/tests/test_qtwisted.py (+1/-1)
To merge this branch: bzr merge lp:~urbanape/ubuntu-sso-client/initial-darwin-port
Reviewer Review Type Date Requested Status
Manuel de la Peña (community) Disapprove
Diego Sarmentero (community) Needs Fixing
Review via email: mp+101112@code.launchpad.net

Description of the change

Embarrassingly small branch to try and get an initial port of the Ubuntu SSO client to Mac OS X.

Right now, we just assume that networking is there.

We also have a hacky way of getting to the candidate callback executables that SSO client will redirect to. On OS X, this might be better served with LaunchServices and custom URI schemes instead of guessing and computing paths (which might not even work in a py2app environment).

There are a few places where a refactoring has taken place to account for darwin and win32 using the same underlying mechanisms (ubuntu_sso/main/perspective_broker.py), and at least one place where it has not (ubuntu_sso/qt/main/windows.py is used by both darwin and windows).

However, in a buildout environment, we are able to at least get a window up and presented (though it just spins and doesn't give focus):

  http://ubuntuone.com/26XHVaWGMZfL8X1HTpN2Yk

From within the 'parts/ubuntu-sso-client' directory, I run the tests like so:

zbir@Howler ~/dev/ubuntuone-windows-installer/scripts/devsetup/parts/ubuntu-sso-client
$ PYTHONPATH=.:/Users/zbir/dev/ubuntuone-windows-installer/scripts/devsetup/parts/ubuntu-sso-client:/Users/zbir/dev/ubuntuone-windows-installer/scripts/devsetup/eggs/ubuntuone_dev_tools-2.99.2-py2.7.egg:/Users/zbir/dev/ubuntuone-windows-installer/scripts/devsetup/lib/python2.7/site-packages /Users/zbir/dev/ubuntuone-windows-installer/scripts/devsetup/bin/python /Users/zbir/dev/ubuntuone-windows-installer/scripts/devsetup/bin/u1trial --reactor=qt4 -i "test_gui.py, test_linux.py, test_qt.py, test_windows.py, test_glib.py, test_txsecrets.py" -p "ubuntu_sso/gtk" --gui ubuntu_sso

To post a comment you must log in.
Revision history for this message
Zachery Bir (urbanape) wrote :

Should mention, that 'parts/ubuntu-sso-client' directory is the result of making a buildout environment:

  https://docs.google.com/a/canonical.com/document/d/1f7xaDT-hblCKIXrKXZjpKtFRsBRnionDix5NojwdgmE

Revision history for this message
Zachery Bir (urbanape) wrote :

> Should mention, that 'parts/ubuntu-sso-client' directory is the result of
> making a buildout environment:
>

https://docs.google.com/a/canonical.com/document/d/1f7xaDT-hblCKIXrKXZjpKtFRsBRnionDix5NojwdgmE/edit

942. By dobey

Add the needed OpenSSL license exception

943. By Natalia Bidart

- When validating the registration screen (in the GTK+ UI), do not force
  accepting the T&C if the tc_url is not defined (LP: #960657).
- Added more logging entries to the registration screen (GTK+ UI) validation
  process.

944. By Manuel de la Peña

- Removed duplicated code from the different webclient implementations (LP: #904842).

Revision history for this message
Manuel de la Peña (mandel) wrote :

Great start for a succeful port, here there are a few comments:

1. There are several new headers added, we had to add an exception due to openssl to the license, can you update them (you can find the new header in trunk).
2. Lets remove alejandro from the headers :)
3. I think we need to refactor the following import so that it does not import from windows:

09 +if sys.platform in ('win32', 'darwin'):
10 from ubuntu_sso.keyring.windows import Keyring
11 else:
12 from ubuntu_sso.keyring.linux import Keyring

I good idea would be to rename the windows.py to something more generic, maybe pykeyring?

4. Same as the above with:

1150 +if sys.platform in ('win32', 'darwin'):
1151 from ubuntu_sso.qt.main import windows
1152 source = windows

5. There is some code duplication here:

1022 +# Internal NetworkManager State constants
1023 +NM_STATE_UNKNOWN = 0
1024 +NM_STATE_UNKNOWN_LIST = [NM_STATE_UNKNOWN]
1025 +NM_STATE_ASLEEP_OLD = 1
1026 +NM_STATE_ASLEEP = 10
1027 +NM_STATE_ASLEEP_LIST = [NM_STATE_ASLEEP_OLD,
1028 + NM_STATE_ASLEEP]
1029 +NM_STATE_CONNECTING_OLD = 2
1030 +NM_STATE_CONNECTING = 40
1031 +NM_STATE_CONNECTING_LIST = [NM_STATE_CONNECTING_OLD,
1032 + NM_STATE_CONNECTING]
1033 +NM_STATE_CONNECTED_OLD = 3
1034 +NM_STATE_CONNECTED_LOCAL = 50
1035 +NM_STATE_CONNECTED_SITE = 60
1036 +NM_STATE_CONNECTED_GLOBAL = 70
1037 +# Specifically don't include local and site, as they won't let us get to server
1038 +NM_STATE_CONNECTED_LIST = [NM_STATE_CONNECTED_OLD,
1039 + NM_STATE_CONNECTED_GLOBAL]
1040 +NM_STATE_DISCONNECTED_OLD = 4
1041 +NM_STATE_DISCONNECTED = 20
1042 +# For us, local and site connections are the same as diconnected
1043 +NM_STATE_DISCONNECTED_LIST = [NM_STATE_DISCONNECTED_OLD,
1044 + NM_STATE_DISCONNECTED,
1045 + NM_STATE_CONNECTED_LOCAL,
1046 + NM_STATE_CONNECTED_SITE]

I think we can add that in a single module to be shared. I'll be adding some bugs following this brach so that we can track our progress.

review: Needs Fixing
945. By Manuel de la Peña

- Fixed all those broken tests on windows related to the dirty reactor left by twisted.pb (LP: #960436).

946. By Manuel de la Peña

- Added the use of the new added webserver that cleans resources correctly (LP: #960436).

947. By Manuel de la Peña

- Fixed tcp activation tests to clean resources correctly (LP: #960436).

948. By Zachery Bir

Merged mandel's branch

949. By Zachery Bir

Shared constants for network states

950. By Zachery Bir

Renamed as a shared resource

951. By Zachery Bir

Use the shared resource, rather than just windows.py

952. By Zachery Bir

Use the shared constants module, and remove some dbus-isms from darwin.py

953. By Zachery Bir

renamed tests to reflect shared resources

954. By Zachery Bir

Trimmed headers

955. By Zachery Bir

Use the new shared networkstates constants, and trim headers.

Revision history for this message
Manuel de la Peña (mandel) wrote :
Download full text (4.2 KiB)

When running the tests on linux I get the following lint issues which will stop tarmac from landing the branch:

ubuntu_sso/main/darwin.py:
    79: [C0301] Line too long (82/79)
    82: [C0301] Line too long (94/79)
    83: [C0301] Line too long (80/79)
    58: [C0111, get_sso_pb_port] Missing docstring
    36: [W0611] Unused import timeout_func
    36: [W0611] Unused import shutdown_func
    36: [W0611] Unused import start_setup
    36: [W0611] Unused import finish_setup
    36: [W0611] Unused import main

ubuntu_sso/main/perspective_broker.py:
    63: [C0103, SSOLoginProxy.CaptchaGenerated] Invalid name "CaptchaGenerated" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    67: [C0103, SSOLoginProxy.CaptchaGenerationError] Invalid name "CaptchaGenerationError" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    76: [C0103, SSOLoginProxy.UserRegistered] Invalid name "UserRegistered" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    80: [C0103, SSOLoginProxy.UserRegistrationError] Invalid name "UserRegistrationError" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    91: [C0103, SSOLoginProxy.LoggedIn] Invalid name "LoggedIn" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    95: [C0103, SSOLoginProxy.LoginError] Invalid name "LoginError" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    99: [C0103, SSOLoginProxy.UserNotValidated] Invalid name "UserNotValidated" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    110: [C0103, SSOLoginProxy.EmailValidated] Invalid name "EmailValidated" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    114: [C0103, SSOLoginProxy.EmailValidationError] Invalid name "EmailValidationError" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    127: [C0103, SSOLoginProxy.PasswordResetTokenSent] Invalid name "PasswordResetTokenSent" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    131: [C0103, SSOLoginProxy.PasswordResetError] Invalid name "PasswordResetError" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    140: [C0103, SSOLoginProxy.PasswordChanged] Invalid name "PasswordChanged" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    144: [C0103, SSOLoginProxy.PasswordChangeError] Invalid name "PasswordChangeError" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    193: [C0103, CredentialsManagementProxy.AuthorizationDenied] Invalid name "AuthorizationDenied" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    197: [C0103, CredentialsManagementProxy.CredentialsFound] Invalid name "CredentialsFound" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    201: [C0103, CredentialsManagementProxy.CredentialsNotFound] Invalid name "CredentialsNotFound" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    205: [C0103, CredentialsManagementProxy.CredentialsCleared] Invalid name "CredentialsCleared" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    209: [C0103, CredentialsManagementProxy.CredentialsStored] Invalid name "CredentialsStored" (should match ([a-z_][a-z0-9_]{2,79}$|setUp|tearDown))
    213: [C0103, CredentialsManagementProxy.CredentialsError] Invalid name "Creden...

Read more...

review: Needs Fixing
956. By Zachery Bir

Clean up long lines and unused imports

957. By Zachery Bir

Move the pylint exclusion

958. By Zachery Bir

Clean up unused imports, move a pylint exclusion to the shared code,
and provide docstrings (and abstract out the get_user_id function)

959. By Zachery Bir

Flow long lines and import SSO_SERVICE_NAME from the right module

Revision history for this message
Manuel de la Peña (mandel) wrote :

I get a segfault when running the tests on linux:

ubuntuone.devtools.testcases.dbus
  DBusTestCase
    runTest ... [OK]
ubuntu_sso.utils.tests.test_txsecrets
  ItemTestCase
    test_delete ... [OK]
    test_delete_prompt ... [OK]
    test_delete_prompt_dismissed ... [OK]
    test_delete_throws_dbus_error ... [OK]
    test_get_value ... [OK]
    test_get_value_throws_dbus_error ... [OK]
  SecretServiceTestCase
    test_create_collection ... [OK]
    test_create_collection_prompt ... [OK]
    test_create_collection_prompt_dismissed ... [OK]
    test_create_collection_throws_dbus_error ... [OK]
    test_get_collections ... [OK]
    test_get_default_collection_created_if_no_default ... [OK]
    test_get_default_collection_created_if_nonexistent ... [OK]
    test_get_default_collection_honours_default_path ... [OK]
    test_get_default_collection_honours_readalias ... [OK]
    test_get_default_collection_is_unlocked_default_path ... [OK]
    test_get_default_collection_is_unlocked_readalias ... [OK]
    test_get_default_collection_set_as_default_if_nonexistent ... [OK]
    test_open_session ... [OK]
    test_open_session_fails_before_opening_as_failure ... [OK]
    test_open_session_throws_dbus_error_as_failure ... [OK]
    test_prompt_accepted ... [OK]
    test_prompt_dismissed ... [OK]
    test_search_items_merges_unlocked_and_locked_items ... [OK]
    test_search_items_merges_unlocked_locked_and_prompt_items ... [OK]
    test_search_locked_items ... [OK]
    test_search_locked_items_prompts ... [OK]
    test_search_locked_items_prompts_dismissed ... [OK]
    test_search_unlocked_items ... [OK]
ubuntu_sso.utils.tests.test_qtwisted
  QtDeferToThreadTestCase
    test_exceptions_become_failures ... QThread: Destroyed while thread is still running
Segmentation fault (core dumped)

review: Needs Fixing
960. By Zachery Bir

Import these names at the appropriate place

961. By Zachery Bir

Provide the name here for Windows tests to pass.

Revision history for this message
Zachery Bir (urbanape) wrote :

That test_qtwisted test used to be test_windows, so can probably be skipped on Linux.

962. By Zachery Bir

Skip the shared code tests between darwin and windows

Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

Why are you doing this??

if sys.platform == 'darwin':
    PREFERED_UI_SIZE['height'] = 533 # I have no idea what I'm doing LOL

review: Needs Information
Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

You should merge your branch with this one:

lp:~diegosarmentero/ubuntu-sso-client/fixing-lint-pep8

That fix all the pep8 and lint issues in this branch.

review: Needs Fixing
963. By Zachery Bir

Merged diegosarmentero's branch, and changed the flippant comment in qt/__init__.py

Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

+1 :D

review: Approve
Revision history for this message
Manuel de la Peña (mandel) wrote :

We have to do something with the following:

PLATFORM_QSS = ":/qtwisted.qss"

This line is going to make us not load the style on windows since there is no data/qt/qtwisted.qss and we will look ugly :(

We can:
1. revert it to be called windows.qss
2. add a darwin and a windows module with the correct qss
3. rename data/qt/windows/qss to qtwisted.qss

Do as you please.

review: Needs Fixing
964. By Zachery Bir

Add a specific darwin.qss, and mediate the proper resource in utils/__init__.py

Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

Hi, i found that now there are new lint errors:

ubuntu_sso/utils/__init__.py:
    52: [C0103] Invalid name "qss_map" (should match (([A-Z_][A-Z0-9_]*)|(__.*__))$)

The name of this should be: QSS_MAP.

Also, i would change this:

if sys.platform in ('win32', 'darwin'):
    from ubuntu_sso.utils import qtwisted as source
    qss_map = {'win32': ':/windows.qss',
               'darwin': ':/darwin.qss',
               }
    source.PLATFORM_QSS = qss_map[sys.platform]
else:
    from ubuntu_sso.utils import linux as source
PLATFORM_QSS = source.PLATFORM_QSS

For:

QSS_MAP = dict(win32=':/windows.qss',
               darwin=':/darwin.qss',
               linux=':/linux.qss')

# Setting linux (fox example) as default if we don't find the
# platform as a key in the dictionary
PLATFORM_QSS = QSS_MAP.get(sys.platform, ":/linux.qss")

review: Needs Fixing
Revision history for this message
Manuel de la Peña (mandel) wrote :

I'll reject the branch because diego has fixed the issues in a diff one and proposed it for merging.

review: Disapprove

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'data/qt/darwin.qss'
2--- data/qt/darwin.qss 1970-01-01 00:00:00 +0000
3+++ data/qt/darwin.qss 2012-04-23 16:23:20 +0000
4@@ -0,0 +1,5 @@
5+/* Styles specific to the windows platform */
6+
7+QWidget {
8+ font-family: "Ubuntu";
9+}
10
11=== modified file 'data/qt/resources.qrc'
12--- data/qt/resources.qrc 2012-03-16 21:24:05 +0000
13+++ data/qt/resources.qrc 2012-04-23 16:23:20 +0000
14@@ -6,6 +6,7 @@
15 <file>../Ubuntu-B.ttf</file>
16 <file>../balloon_shape.png</file>
17 <file>stylesheet.qss</file>
18+ <file>darwin.qss</file>
19 <file>windows.qss</file>
20 <file>linux.qss</file>
21 </qresource>
22
23=== modified file 'run-tests'
24--- run-tests 2012-04-09 17:38:24 +0000
25+++ run-tests 2012-04-23 16:23:20 +0000
26@@ -59,7 +59,7 @@
27 fi
28
29 echo "*** Running GTK test suite for ""$MODULE"" ***"
30-$XVFB_CMDLINE u1trial --reactor=gi --gui -p "$QT_TESTS_PATH" -i "test_windows.py,test_qt.py" "$MODULE"
31+$XVFB_CMDLINE u1trial --reactor=gi --gui -p "$QT_TESTS_PATH" -i "test_windows.py,test_qt.py,test_qtwisted.py,test_pykeyring.py" "$MODULE"
32 rm -rf _trial_temp
33
34 # hack to avoid:
35
36=== modified file 'ubuntu_sso/keyring/__init__.py'
37--- ubuntu_sso/keyring/__init__.py 2012-04-09 17:38:24 +0000
38+++ ubuntu_sso/keyring/__init__.py 2012-04-23 16:23:20 +0000
39@@ -1,8 +1,4 @@
40 # -*- coding: utf-8 -*-
41-# Authors:
42-# Andrew Higginson
43-# Alejandro J. Cura <alecu@canonical.com>
44-# Manuel de la Pena <manuel@canonical.com>
45 #
46 # Copyright 2011-2012 Canonical Ltd.
47 #
48@@ -104,8 +100,8 @@
49 returnValue(None)
50
51
52-if sys.platform == 'win32':
53- from ubuntu_sso.keyring.windows import Keyring
54+if sys.platform in ('win32', 'darwin'):
55+ from ubuntu_sso.keyring.pykeyring import Keyring
56 else:
57 from ubuntu_sso.keyring.linux import Keyring
58
59
60=== modified file 'ubuntu_sso/keyring/linux.py'
61--- ubuntu_sso/keyring/linux.py 2012-04-09 17:38:24 +0000
62+++ ubuntu_sso/keyring/linux.py 2012-04-23 16:23:20 +0000
63@@ -2,12 +2,6 @@
64 #
65 # Copyright (C) 2010-2012 Canonical
66 #
67-# Authors:
68-# Andrew Higginson
69-# Alejandro J. Cura <alecu@canonical.com>
70-# Natalia B. Bidart <natalia.bidart@canonical.com>
71-# Manuel de la Pena <manuel@canonical.com>
72-#
73 # This program is free software; you can redistribute it and/or modify it under
74 # the terms of the GNU General Public License as published by the Free Software
75 # Foundation; version 3.
76
77=== renamed file 'ubuntu_sso/keyring/windows.py' => 'ubuntu_sso/keyring/pykeyring.py'
78--- ubuntu_sso/keyring/windows.py 2012-04-09 17:38:24 +0000
79+++ ubuntu_sso/keyring/pykeyring.py 2012-04-23 16:23:20 +0000
80@@ -1,5 +1,4 @@
81 # -*- coding: utf-8 -*-
82-# Author: Manuel de la Pena <manuel@canonical.com>
83 #
84 # Copyright 2011-2012 Canonical Ltd.
85 #
86@@ -27,11 +26,11 @@
87 # do not wish to do so, delete this exception statement from your
88 # version. If you delete this exception statement from all source
89 # files in the program, then also delete it here.
90-"""Keyring implementation on Windows."""
91+"""Keyring implementation for Windows and Darwin."""
92
93 from json import loads, dumps
94
95-from ubuntu_sso.utils.windows import qtDeferToThread as deferToThread
96+from ubuntu_sso.utils.qtwisted import qtDeferToThread as deferToThread
97
98 USERNAME = 'ubuntu_sso'
99
100
101=== renamed file 'ubuntu_sso/keyring/tests/test_windows.py' => 'ubuntu_sso/keyring/tests/test_pykeyring.py'
102--- ubuntu_sso/keyring/tests/test_windows.py 2012-04-09 17:38:24 +0000
103+++ ubuntu_sso/keyring/tests/test_pykeyring.py 2012-04-23 16:23:20 +0000
104@@ -1,5 +1,4 @@
105 # -*- coding: utf-8 -*-
106-# Author: Manuel de la Pena <manuel@canonical.com>
107 #
108 # Copyright 2011-2012 Canonical Ltd.
109 #
110@@ -27,14 +26,14 @@
111 # do not wish to do so, delete this exception statement from your
112 # version. If you delete this exception statement from all source
113 # files in the program, then also delete it here.
114-"""Test the windows keyring implementation."""
115+"""Test the pykeyring implementation."""
116
117 from json import dumps
118
119 from twisted.internet import defer
120 from twisted.trial.unittest import TestCase
121
122-from ubuntu_sso.keyring import windows
123+from ubuntu_sso.keyring import pykeyring
124
125
126 class FakePyKeyring(object):
127@@ -71,24 +70,24 @@
128 return func(args)
129
130
131-class TestWindowsKeyring(TestCase):
132- """Test the windows keyring implementation."""
133+class TestPyKeyring(TestCase):
134+ """Test the pykeyring implementation."""
135
136 @defer.inlineCallbacks
137 def setUp(self):
138 """Setup tests."""
139- yield super(TestWindowsKeyring, self).setUp()
140+ yield super(TestPyKeyring, self).setUp()
141 self.app_name = 'app_name'
142 self.fake_keyring = FakePyKeyring()
143- self.keyring = windows.Keyring(self.fake_keyring)
144+ self.keyring = pykeyring.Keyring(self.fake_keyring)
145 self.fake_defer = FakeDefer()
146- self.patch(windows, "deferToThread",
147+ self.patch(pykeyring, "deferToThread",
148 self.fake_defer.fake_defer_to_thread)
149
150 # pylint: disable=E1103
151 def test_set_credentials(self):
152 """Test setting the credentials."""
153- self.keyring = windows.Keyring(self.fake_keyring)
154+ self.keyring = pykeyring.Keyring(self.fake_keyring)
155 d = self.keyring.set_credentials(self.app_name, 'password')
156 self.assertEqual(d.data['set_password'],
157 (('app_name', 'ubuntu_sso', '"password"'), ))
158
159=== modified file 'ubuntu_sso/main/__init__.py'
160--- ubuntu_sso/main/__init__.py 2012-04-10 10:43:48 +0000
161+++ ubuntu_sso/main/__init__.py 2012-04-23 16:23:20 +0000
162@@ -57,12 +57,38 @@
163 logger = setup_logging("ubuntu_sso.main")
164 TIMEOUT_INTERVAL = 10000 # 10 seconds
165
166-# pylint: disable=C0103, W0703
167+# pylint: disable=C0103, W0703, W0621
168
169 if sys.platform == 'win32':
170 from ubuntu_sso.main import windows
171+ from ubuntu_sso.main.perspective_broker import (
172+ timeout_func,
173+ shutdown_func,
174+ start_setup,
175+ main as main_func,
176+ )
177+
178 source = windows
179+ source.timeout_func = timeout_func
180+ source.shutdown_func = shutdown_func
181+ source.start_setup = start_setup
182+ source.main = main_func
183+
184 TIMEOUT_INTERVAL = 10000000000 # forever (hack)
185+elif sys.platform == 'darwin':
186+ from ubuntu_sso.main import darwin
187+ from ubuntu_sso.main.perspective_broker import (
188+ timeout_func,
189+ shutdown_func,
190+ start_setup,
191+ main as main_func,
192+ )
193+
194+ source = darwin
195+ source.timeout_func = timeout_func
196+ source.shutdown_func = shutdown_func
197+ source.start_setup = start_setup
198+ source.main = main_func
199 else:
200 from ubuntu_sso.main import linux
201 source = linux
202
203=== added file 'ubuntu_sso/main/darwin.py'
204--- ubuntu_sso/main/darwin.py 1970-01-01 00:00:00 +0000
205+++ ubuntu_sso/main/darwin.py 2012-04-23 16:23:20 +0000
206@@ -0,0 +1,110 @@
207+# -*- coding: utf-8 -*-
208+#
209+# Copyright 2009-2012 Canonical Ltd.
210+#
211+# This program is free software: you can redistribute it and/or modify it
212+# under the terms of the GNU General Public License version 3, as published
213+# by the Free Software Foundation.
214+#
215+# This program is distributed in the hope that it will be useful, but
216+# WITHOUT ANY WARRANTY; without even the implied warranties of
217+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
218+# PURPOSE. See the GNU General Public License for more details.
219+#
220+# You should have received a copy of the GNU General Public License along
221+# with this program. If not, see <http://www.gnu.org/licenses/>.
222+"""Main module implementation specific for darwin (OS X).
223+
224+This module should never import from the multiplatform one (main/__init__.py),
225+but the other way around. Likewise, this module should *not* have any logic
226+regarding error processing or decision making about when to send a given
227+signal.
228+
229+Also, most of the logging is being made in the main module to avoid
230+duplication between the different platform implementations.
231+
232+"""
233+
234+import os
235+import os.path
236+
237+from twisted.internet import defer
238+
239+import ubuntu_sso
240+from ubuntu_sso.logger import setup_logging
241+from ubuntu_sso.utils.ipc import BaseClient
242+from ubuntu_sso.main.perspective_broker import (
243+ SSO_SERVICE_NAME,
244+ SSOLoginClient,
245+ CredentialsManagementClient,
246+ UbuntuSSOProxyBase,
247+ )
248+
249+# Invalid name for signals that are CamelCase
250+# pylint: disable=C0103
251+
252+
253+logger = setup_logging("ubuntu_sso.main.darwin")
254+U1_REG_PATH = r'Software\Ubuntu One'
255+SSO_INSTALL_PATH = 'SSOInstallPath'
256+SSO_BASE_PB_PORT = 50000
257+SSO_RESERVED_PORTS = 3000
258+SSO_PORT_ALLOCATION_STEP = 3 # contiguous ports for sso, u1client, and u1cp
259+
260+
261+def get_sso_pb_port():
262+ """Compute the port the SSO service should run on per-user."""
263+ uid_modulo = os.getuid() % SSO_RESERVED_PORTS
264+ return SSO_BASE_PB_PORT + uid_modulo * SSO_PORT_ALLOCATION_STEP
265+
266+
267+class UbuntuSSOProxy(UbuntuSSOProxyBase):
268+ """Object that exposes the diff referenceable objects."""
269+
270+ name = SSO_SERVICE_NAME
271+
272+ @property
273+ def port(self):
274+ """Get the port on which the SSO pb is running."""
275+ return get_sso_pb_port()
276+
277+ @property
278+ def cmdline(self):
279+ """Get the command line to activate an executable."""
280+ # MAYBE: use special URI schemes to have LaunchServices handle
281+ # running the app, instead of paths to scripts
282+ # e.g. 'x-ubuntuone-syncdaemon', 'x-ubuntu-sso-login'
283+ ubuntu_sso_pkg_dir = os.path.dirname(
284+ os.path.dirname(ubuntu_sso.__file__))
285+ ubuntu_sso_bin_dir = os.path.join(ubuntu_sso_pkg_dir, 'bin')
286+ ubuntu_sso_bin = os.path.join(ubuntu_sso_bin_dir, self.name)
287+ ubuntuone_client_bin_dir = os.path.join(ubuntu_sso_pkg_dir,
288+ '../ubuntuone-client/bin')
289+ ubuntuone_client_bin = os.path.join(ubuntuone_client_bin_dir,
290+ self.name)
291+ if os.path.exists(ubuntuone_client_bin):
292+ return ubuntuone_client_bin
293+ return ubuntu_sso_bin
294+
295+
296+class UbuntuSSOClient(BaseClient):
297+ """Base client that provides remote access to the sso API."""
298+
299+ name = SSO_SERVICE_NAME
300+
301+ clients = {
302+ 'sso_login': SSOLoginClient,
303+ 'cred_manager': CredentialsManagementClient,
304+ }
305+
306+ service_name = UbuntuSSOProxy.name
307+ service_port = UbuntuSSOProxy.port
308+ service_cmdline = UbuntuSSOProxy.cmdline
309+
310+
311+@defer.inlineCallbacks
312+def get_sso_client():
313+ """Get a client to access the SSO service."""
314+ result = UbuntuSSOClient()
315+ yield result.connect()
316+ defer.returnValue(result)
317
318=== added file 'ubuntu_sso/main/perspective_broker.py'
319--- ubuntu_sso/main/perspective_broker.py 1970-01-01 00:00:00 +0000
320+++ ubuntu_sso/main/perspective_broker.py 2012-04-23 16:23:20 +0000
321@@ -0,0 +1,352 @@
322+# -*- coding: utf-8 -*-
323+#
324+# Copyright 2012 Canonical Ltd.
325+#
326+# This program is free software: you can redistribute it and/or modify it
327+# under the terms of the GNU General Public License version 3, as published
328+# by the Free Software Foundation.
329+#
330+# This program is distributed in the hope that it will be useful, but
331+# WITHOUT ANY WARRANTY; without even the implied warranties of
332+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
333+# PURPOSE. See the GNU General Public License for more details.
334+#
335+# You should have received a copy of the GNU General Public License along
336+# with this program. If not, see <http://www.gnu.org/licenses/>.
337+"""Generic SSO client using Twisted Perspective Broker.
338+
339+This module should never import from the multiplatform one (main/__init__.py),
340+but the other way around. Likewise, this module should *not* have any logic
341+regarding error processing or decision making about when to send a given
342+signal.
343+
344+Also, most of the logging is being made in the main module to avoid
345+duplication between the different platform implementations.
346+
347+"""
348+
349+from twisted.internet.task import LoopingCall
350+from twisted.python.failure import Failure
351+
352+from ubuntu_sso.logger import setup_logging
353+from ubuntu_sso.utils.ipc import (
354+ BaseService,
355+ RemoteClient,
356+ RemoteService,
357+ signal,
358+)
359+
360+logger = setup_logging("ubuntu_sso.main.perspective_broker")
361+SSO_SERVICE_NAME = "ubuntu-sso-client"
362+
363+# Invalid name for signals that are CamelCase
364+# pylint: disable=C0103
365+
366+
367+class SSOLoginProxy(RemoteService):
368+ """Login thru the Single Sign On service."""
369+
370+ remote_calls = [
371+ 'generate_captcha',
372+ 'register_user',
373+ 'login',
374+ 'login_and_ping',
375+ 'validate_email',
376+ 'validate_email_and_ping',
377+ 'request_password_reset_token',
378+ 'set_new_password',
379+ ]
380+
381+ def __init__(self, root, *args, **kwargs):
382+ super(SSOLoginProxy, self).__init__(*args, **kwargs)
383+ self.root = root
384+
385+ # generate_capcha signals
386+ @signal
387+ def CaptchaGenerated(self, app_name, result):
388+ """Signal thrown after the captcha is generated."""
389+
390+ @signal
391+ def CaptchaGenerationError(self, app_name, error):
392+ """Signal thrown when there's a problem generating the captcha."""
393+
394+ def generate_captcha(self, app_name, filename):
395+ """Call the matching method in the processor."""
396+ self.root.sso_login.generate_captcha(app_name, filename)
397+
398+ # register_user signals
399+ @signal
400+ def UserRegistered(self, app_name, result):
401+ """Signal thrown when the user is registered."""
402+
403+ @signal
404+ def UserRegistrationError(self, app_name, error):
405+ """Signal thrown when there's a problem registering the user."""
406+
407+ def register_user(self, app_name, email, password, name,
408+ captcha_id, captcha_solution):
409+ """Call the matching method in the processor."""
410+ self.root.sso_login.register_user(app_name, email, password, name,
411+ captcha_id, captcha_solution)
412+
413+ # login signals
414+ @signal
415+ def LoggedIn(self, app_name, result):
416+ """Signal thrown when the user is logged in."""
417+
418+ @signal
419+ def LoginError(self, app_name, error):
420+ """Signal thrown when there is a problem in the login."""
421+
422+ @signal
423+ def UserNotValidated(self, app_name, result):
424+ """Signal thrown when the user is not validated."""
425+
426+ def login(self, app_name, email, password, ping_url=None):
427+ """Call the matching method in the processor."""
428+ self.root.sso_login.login(app_name, email, password, ping_url)
429+
430+ login_and_ping = login
431+
432+ # validate_email signals
433+ @signal
434+ def EmailValidated(self, app_name, result):
435+ """Signal thrown after the email is validated."""
436+
437+ @signal
438+ def EmailValidationError(self, app_name, error):
439+ """Signal thrown when there's a problem validating the email."""
440+
441+ def validate_email(self, app_name, email, password, email_token,
442+ ping_url=None):
443+ """Call the matching method in the processor."""
444+ self.root.sso_login.validate_email(app_name,
445+ email, password, email_token, ping_url)
446+
447+ validate_email_and_ping = validate_email
448+
449+ # request_password_reset_token signals
450+ @signal
451+ def PasswordResetTokenSent(self, app_name, result):
452+ """Signal thrown when the token is succesfully sent."""
453+
454+ @signal
455+ def PasswordResetError(self, app_name, error):
456+ """Signal thrown when there's a problem sending the token."""
457+
458+ def request_password_reset_token(self, app_name, email):
459+ """Call the matching method in the processor."""
460+ self.root.sso_login.request_password_reset_token(app_name, email)
461+
462+ # set_new_password signals
463+ @signal
464+ def PasswordChanged(self, app_name, result):
465+ """Signal thrown when the token is succesfully sent."""
466+
467+ @signal
468+ def PasswordChangeError(self, app_name, error):
469+ """Signal thrown when there's a problem sending the token."""
470+
471+ def set_new_password(self, app_name, email, token, new_password):
472+ """Call the matching method in the processor."""
473+ self.root.sso_login.set_new_password(app_name,
474+ email, token, new_password)
475+
476+
477+class CredentialsManagementProxy(RemoteService):
478+ """Object that manages credentials.
479+
480+ Every exposed method in this class requires one mandatory argument:
481+
482+ - 'app_name': the name of the application. Will be displayed in the
483+ GUI header, plus it will be used to find/build/clear tokens.
484+
485+ And accepts another parameter named 'args', which is a dictionary that
486+ can contain the following:
487+
488+ - 'help_text': an explanatory text for the end-users, will be
489+ shown below the header. This is an optional free text field.
490+
491+ - 'ping_url': the url to open after successful token retrieval. If
492+ defined, the email will be attached to the url and will be pinged
493+ with a OAuth-signed request.
494+
495+ - 'tc_url': the link to the Terms and Conditions page. If defined,
496+ the checkbox to agree to the terms will link to it.
497+
498+ - 'window_id': the id of the window which will be set as a parent
499+ of the GUI. If not defined, no parent will be set.
500+
501+ """
502+
503+ remote_calls = [
504+ 'find_credentials',
505+ 'clear_credentials',
506+ 'store_credentials',
507+ 'register',
508+ 'login',
509+ 'login_email_password',
510+ ]
511+
512+ def __init__(self, root, *args, **kwargs):
513+ super(CredentialsManagementProxy, self).__init__(*args, **kwargs)
514+ self.root = root
515+
516+ @signal
517+ def AuthorizationDenied(self, app_name):
518+ """Signal thrown when the user denies the authorization."""
519+
520+ @signal
521+ def CredentialsFound(self, app_name, credentials):
522+ """Signal thrown when the credentials are found."""
523+
524+ @signal
525+ def CredentialsNotFound(self, app_name):
526+ """Signal thrown when the credentials are not found."""
527+
528+ @signal
529+ def CredentialsCleared(self, app_name):
530+ """Signal thrown when the credentials were cleared."""
531+
532+ @signal
533+ def CredentialsStored(self, app_name):
534+ """Signal thrown when the credentials were cleared."""
535+
536+ @signal
537+ def CredentialsError(self, app_name, error_dict):
538+ """Signal thrown when there is a problem getting the credentials."""
539+
540+ def find_credentials(self, app_name, args):
541+ """Look for the credentials for an application.
542+
543+ - 'app_name': the name of the application which credentials are
544+ going to be removed.
545+
546+ - 'args' is a dictionary, currently not used.
547+
548+ """
549+ self.root.cred_manager.find_credentials(app_name, args)
550+
551+ def clear_credentials(self, app_name, args):
552+ """Clear the credentials for an application.
553+
554+ - 'app_name': the name of the application which credentials are
555+ going to be removed.
556+
557+ - 'args' is a dictionary, currently not used.
558+
559+ """
560+ self.root.cred_manager.clear_credentials(app_name, args)
561+
562+ def store_credentials(self, app_name, args):
563+ """Store the token for an application.
564+
565+ - 'app_name': the name of the application which credentials are
566+ going to be stored.
567+
568+ - 'args' is the dictionary holding the credentials. Needs to provide
569+ the following mandatory keys: 'token', 'token_key', 'consumer_key',
570+ 'consumer_secret'.
571+
572+ """
573+ self.root.cred_manager.store_credentials(app_name, args)
574+
575+ def register(self, app_name, args):
576+ """Get credentials if found else prompt GUI to register."""
577+ self.root.cred_manager.register(app_name, args)
578+
579+ def login(self, app_name, args):
580+ """Get credentials if found else prompt GUI to login."""
581+ self.root.cred_manager.login(app_name, args)
582+
583+ def login_email_password(self, app_name, args):
584+ """Get credentials if found, else login using email and password.
585+
586+ - 'args' should contain at least the follwing keys: 'email' and
587+ 'password'. Those will be used to issue a new SSO token, which will be
588+ returned trough the CredentialsFound signal.
589+
590+ """
591+ self.root.cred_manager.login_email_password(app_name, args)
592+
593+
594+class UbuntuSSOProxyBase(BaseService):
595+ """Object that exposes the diff referenceable objects."""
596+
597+ services = {
598+ 'sso_login': SSOLoginProxy,
599+ 'cred_manager': CredentialsManagementProxy,
600+ }
601+
602+ name = SSO_SERVICE_NAME
603+
604+
605+# ============================== client classes ==============================
606+
607+
608+class SSOLoginClient(RemoteClient):
609+ """Client that can perform calls to the remote SSOLogin object."""
610+
611+ call_remote_functions = SSOLoginProxy.remote_calls
612+ signal_handlers = [
613+ 'CaptchaGenerated',
614+ 'CaptchaGenerationError',
615+ 'UserRegistered',
616+ 'UserRegistrationError',
617+ 'LoggedIn',
618+ 'LoginError',
619+ 'UserNotValidated',
620+ 'EmailValidated',
621+ 'EmailValidationError',
622+ 'PasswordResetTokenSent',
623+ 'PasswordResetError',
624+ 'PasswordChanged',
625+ 'PasswordChangeError',
626+ ]
627+
628+
629+class CredentialsManagementClient(RemoteClient):
630+ """Client that can perform calls to the remote CredManagement object."""
631+
632+ call_remote_functions = CredentialsManagementProxy.remote_calls
633+ signal_handlers = [
634+ 'AuthorizationDenied',
635+ 'CredentialsFound',
636+ 'CredentialsNotFound',
637+ 'CredentialsCleared',
638+ 'CredentialsStored',
639+ 'CredentialsError',
640+ ]
641+
642+
643+def add_timeout(interval, callback, *args, **kwargs):
644+ """Add a timeout callback as a task."""
645+ time_out_task = LoopingCall(callback, *args, **kwargs)
646+ time_out_task.start(interval / 1000, now=False)
647+
648+
649+timeout_func = add_timeout
650+start_setup = lambda *a, **kw: None
651+
652+# the reactor does have run and stop methods
653+# pylint: disable=E1101
654+
655+
656+def shutdown_func():
657+ """Stop the reactor."""
658+ from twisted.internet import reactor
659+ reactor.stop()
660+
661+
662+def finish_setup(result, loop):
663+ """Stop the reactor if a failure ocurred."""
664+ if isinstance(result, Failure):
665+ shutdown_func()
666+
667+
668+def main():
669+ """Run the specific mainloop."""
670+ from twisted.internet import reactor
671+ reactor.run()
672+
673+# pylint: enable=E1101
674
675=== added file 'ubuntu_sso/main/tests/test_darwin.py'
676--- ubuntu_sso/main/tests/test_darwin.py 1970-01-01 00:00:00 +0000
677+++ ubuntu_sso/main/tests/test_darwin.py 2012-04-23 16:23:20 +0000
678@@ -0,0 +1,43 @@
679+# -*- coding: utf-8 -*-
680+#
681+# Copyright 2012 Canonical Ltd.
682+#
683+# This program is free software: you can redistribute it and/or modify it
684+# under the terms of the GNU General Public License version 3, as published
685+# by the Free Software Foundation.
686+#
687+# This program is distributed in the hope that it will be useful, but
688+# WITHOUT ANY WARRANTY; without even the implied warranties of
689+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
690+# PURPOSE. See the GNU General Public License for more details.
691+#
692+# You should have received a copy of the GNU General Public License along
693+# with this program. If not, see <http://www.gnu.org/licenses/>.
694+"""Darwin specific tests for the main module."""
695+
696+from ubuntu_sso.main import darwin
697+from ubuntu_sso.tests import TestCase
698+
699+# because we are using twisted we have java like names C0103
700+# pylint: disable=C0103
701+
702+MOCK_SSO_DARWIN_UID = 1001
703+
704+
705+class MockOsGetuid(object):
706+ """Mocked os.getuid to support reliable uid for test"""
707+
708+ def getuid(self):
709+ """Return the same uid for tests"""
710+ return MOCK_SSO_DARWIN_UID
711+
712+
713+class DarwinTestCase(TestCase):
714+ """Tests for module level misc functions"""
715+
716+ def test_get_sso_pb_port(self):
717+ """Test the get_sso_pb_port function, by patching os.getuid"""
718+ mock_os = MockOsGetuid()
719+ self.patch(darwin, "os", mock_os)
720+ expected_port = 53003
721+ self.assertEqual(darwin.get_sso_pb_port(), expected_port)
722
723=== modified file 'ubuntu_sso/main/tests/test_windows.py'
724--- ubuntu_sso/main/tests/test_windows.py 2012-04-09 17:38:24 +0000
725+++ ubuntu_sso/main/tests/test_windows.py 2012-04-23 16:23:20 +0000
726@@ -32,6 +32,7 @@
727 from _winreg import REG_SZ
728
729 from ubuntu_sso.main import windows
730+from ubuntu_sso.main import perspective_broker
731 from ubuntu_sso.tests import TestCase
732
733 # because we are using twisted we have java like names C0103
734@@ -110,5 +111,6 @@
735 sample_value = "test 123"
736 self.patch(windows, "OpenKey", lambda key, subkey: sample_value)
737 self.patch(windows, "QueryValueEx", lambda key, v: (key, REG_SZ))
738- cmdline = windows.get_activation_cmdline(windows.SSO_SERVICE_NAME)
739+ cmdline = windows.get_activation_cmdline(
740+ perspective_broker.SSO_SERVICE_NAME)
741 self.assertEqual(sample_value, cmdline)
742
743=== modified file 'ubuntu_sso/main/windows.py'
744--- ubuntu_sso/main/windows.py 2012-04-09 17:38:24 +0000
745+++ ubuntu_sso/main/windows.py 2012-04-23 16:23:20 +0000
746@@ -45,350 +45,71 @@
747 # pylint: enable=F0401
748
749 from twisted.internet import defer
750-from twisted.internet.task import LoopingCall
751-from twisted.python.failure import Failure
752
753 from ubuntu_sso.logger import setup_logging
754-from ubuntu_sso.utils.ipc import (
755- BaseClient,
756- BaseService,
757- RemoteClient,
758- RemoteService,
759- signal,
760-)
761-
762-
763-# Invalid name for signals that are CamelCase
764-# pylint: disable=C0103
765+from ubuntu_sso.utils.ipc import BaseClient
766+from ubuntu_sso.main.perspective_broker import (
767+ SSO_SERVICE_NAME,
768+ SSOLoginClient,
769+ CredentialsManagementClient,
770+ UbuntuSSOProxyBase,
771+ )
772
773
774 logger = setup_logging("ubuntu_sso.main.windows")
775-NAMED_PIPE_URL = '\\\\.\\pipe\\ubuntu_sso\\%s'
776 U1_REG_PATH = r'Software\Ubuntu One'
777 SSO_INSTALL_PATH = 'SSOInstallPath'
778 SSO_BASE_PB_PORT = 50000
779 SSO_RESERVED_PORTS = 3000
780 SSO_PORT_ALLOCATION_STEP = 3 # contiguous ports for sso, u1client, and u1cp
781-SSO_SERVICE_NAME = "ubuntu-sso-client"
782
783
784 def get_user_id():
785- """Find the numeric user id."""
786+ """Compute the user id of the currently running process."""
787 process_handle = win32process.GetCurrentProcess()
788 token_handle = win32security.OpenProcessToken(process_handle,
789 win32security.TOKEN_ALL_ACCESS)
790 user_sid = win32security.GetTokenInformation(token_handle,
791 win32security.TokenUser)[0]
792 sid_parts = str(user_sid).split("-")
793- uid = int(sid_parts[-1])
794- return uid
795+ return int(sid_parts[-1])
796
797
798 def get_sso_pb_port():
799- """Get the port on which the SSO pb is running."""
800- uid = get_user_id()
801- uid_modulo = uid % SSO_RESERVED_PORTS
802- port = SSO_BASE_PB_PORT + uid_modulo * SSO_PORT_ALLOCATION_STEP
803- return port
804-
805-
806-def get_activation_cmdline(service_name):
807- """Get the command line to activate an executable."""
808+ """Compute the port the SSO service should run on per-user."""
809+ uid_modulo = get_user_id() % SSO_RESERVED_PORTS
810+ return SSO_BASE_PB_PORT + uid_modulo * SSO_PORT_ALLOCATION_STEP
811+
812+
813+def get_activation_cmdline(name):
814+ """Find the path to the executable callback."""
815 key = OpenKey(HKEY_LOCAL_MACHINE, U1_REG_PATH)
816 # pylint: disable=W0612
817- value, registry_type = QueryValueEx(key, "path-" + service_name)
818+ value, registry_type = QueryValueEx(key, "path-" + name)
819 return value
820
821
822-class SSOLoginProxy(RemoteService):
823- """Login thru the Single Sign On service."""
824-
825- remote_calls = [
826- 'generate_captcha',
827- 'register_user',
828- 'login',
829- 'login_and_ping',
830- 'validate_email',
831- 'validate_email_and_ping',
832- 'request_password_reset_token',
833- 'set_new_password',
834- ]
835-
836- def __init__(self, root, *args, **kwargs):
837- super(SSOLoginProxy, self).__init__(*args, **kwargs)
838- self.root = root
839-
840- # generate_capcha signals
841- @signal
842- def CaptchaGenerated(self, app_name, result):
843- """Signal thrown after the captcha is generated."""
844-
845- @signal
846- def CaptchaGenerationError(self, app_name, error):
847- """Signal thrown when there's a problem generating the captcha."""
848-
849- def generate_captcha(self, app_name, filename):
850- """Call the matching method in the processor."""
851- self.root.sso_login.generate_captcha(app_name, filename)
852-
853- # register_user signals
854- @signal
855- def UserRegistered(self, app_name, result):
856- """Signal thrown when the user is registered."""
857-
858- @signal
859- def UserRegistrationError(self, app_name, error):
860- """Signal thrown when there's a problem registering the user."""
861-
862- def register_user(self, app_name, email, password, name,
863- captcha_id, captcha_solution):
864- """Call the matching method in the processor."""
865- self.root.sso_login.register_user(app_name, email, password, name,
866- captcha_id, captcha_solution)
867-
868- # login signals
869- @signal
870- def LoggedIn(self, app_name, result):
871- """Signal thrown when the user is logged in."""
872-
873- @signal
874- def LoginError(self, app_name, error):
875- """Signal thrown when there is a problem in the login."""
876-
877- @signal
878- def UserNotValidated(self, app_name, result):
879- """Signal thrown when the user is not validated."""
880-
881- def login(self, app_name, email, password, ping_url=None):
882- """Call the matching method in the processor."""
883- self.root.sso_login.login(app_name, email, password, ping_url)
884-
885- login_and_ping = login
886-
887- # validate_email signals
888- @signal
889- def EmailValidated(self, app_name, result):
890- """Signal thrown after the email is validated."""
891-
892- @signal
893- def EmailValidationError(self, app_name, error):
894- """Signal thrown when there's a problem validating the email."""
895-
896- def validate_email(self, app_name, email, password, email_token,
897- ping_url=None):
898- """Call the matching method in the processor."""
899- self.root.sso_login.validate_email(app_name,
900- email, password, email_token, ping_url)
901-
902- validate_email_and_ping = validate_email
903-
904- # request_password_reset_token signals
905- @signal
906- def PasswordResetTokenSent(self, app_name, result):
907- """Signal thrown when the token is succesfully sent."""
908-
909- @signal
910- def PasswordResetError(self, app_name, error):
911- """Signal thrown when there's a problem sending the token."""
912-
913- def request_password_reset_token(self, app_name, email):
914- """Call the matching method in the processor."""
915- self.root.sso_login.request_password_reset_token(app_name, email)
916-
917- # set_new_password signals
918- @signal
919- def PasswordChanged(self, app_name, result):
920- """Signal thrown when the token is succesfully sent."""
921-
922- @signal
923- def PasswordChangeError(self, app_name, error):
924- """Signal thrown when there's a problem sending the token."""
925-
926- def set_new_password(self, app_name, email, token, new_password):
927- """Call the matching method in the processor."""
928- self.root.sso_login.set_new_password(app_name,
929- email, token, new_password)
930-
931-
932-class CredentialsManagementProxy(RemoteService):
933- """Object that manages credentials.
934-
935- Every exposed method in this class requires one mandatory argument:
936-
937- - 'app_name': the name of the application. Will be displayed in the
938- GUI header, plus it will be used to find/build/clear tokens.
939-
940- And accepts another parameter named 'args', which is a dictionary that
941- can contain the following:
942-
943- - 'help_text': an explanatory text for the end-users, will be
944- shown below the header. This is an optional free text field.
945-
946- - 'ping_url': the url to open after successful token retrieval. If
947- defined, the email will be attached to the url and will be pinged
948- with a OAuth-signed request.
949-
950- - 'tc_url': the link to the Terms and Conditions page. If defined,
951- the checkbox to agree to the terms will link to it.
952-
953- - 'window_id': the id of the window which will be set as a parent
954- of the GUI. If not defined, no parent will be set.
955-
956- """
957-
958- remote_calls = [
959- 'find_credentials',
960- 'clear_credentials',
961- 'store_credentials',
962- 'register',
963- 'login',
964- 'login_email_password',
965- ]
966-
967- def __init__(self, root, *args, **kwargs):
968- super(CredentialsManagementProxy, self).__init__(*args, **kwargs)
969- self.root = root
970-
971- @signal
972- def AuthorizationDenied(self, app_name):
973- """Signal thrown when the user denies the authorization."""
974-
975- @signal
976- def CredentialsFound(self, app_name, credentials):
977- """Signal thrown when the credentials are found."""
978-
979- @signal
980- def CredentialsNotFound(self, app_name):
981- """Signal thrown when the credentials are not found."""
982-
983- @signal
984- def CredentialsCleared(self, app_name):
985- """Signal thrown when the credentials were cleared."""
986-
987- @signal
988- def CredentialsStored(self, app_name):
989- """Signal thrown when the credentials were cleared."""
990-
991- @signal
992- def CredentialsError(self, app_name, error_dict):
993- """Signal thrown when there is a problem getting the credentials."""
994-
995- def find_credentials(self, app_name, args):
996- """Look for the credentials for an application.
997-
998- - 'app_name': the name of the application which credentials are
999- going to be removed.
1000-
1001- - 'args' is a dictionary, currently not used.
1002-
1003- """
1004- self.root.cred_manager.find_credentials(app_name, args)
1005-
1006- def clear_credentials(self, app_name, args):
1007- """Clear the credentials for an application.
1008-
1009- - 'app_name': the name of the application which credentials are
1010- going to be removed.
1011-
1012- - 'args' is a dictionary, currently not used.
1013-
1014- """
1015- self.root.cred_manager.clear_credentials(app_name, args)
1016-
1017- def store_credentials(self, app_name, args):
1018- """Store the token for an application.
1019-
1020- - 'app_name': the name of the application which credentials are
1021- going to be stored.
1022-
1023- - 'args' is the dictionary holding the credentials. Needs to provide
1024- the following mandatory keys: 'token', 'token_key', 'consumer_key',
1025- 'consumer_secret'.
1026-
1027- """
1028- self.root.cred_manager.store_credentials(app_name, args)
1029-
1030- def register(self, app_name, args):
1031- """Get credentials if found else prompt GUI to register."""
1032- self.root.cred_manager.register(app_name, args)
1033-
1034- def login(self, app_name, args):
1035- """Get credentials if found else prompt GUI to login."""
1036- self.root.cred_manager.login(app_name, args)
1037-
1038- def login_email_password(self, app_name, args):
1039- """Get credentials if found, else login using email and password.
1040-
1041- - 'args' should contain at least the follwing keys: 'email' and
1042- 'password'. Those will be used to issue a new SSO token, which will be
1043- returned trough the CredentialsFound signal.
1044-
1045- """
1046- self.root.cred_manager.login_email_password(app_name, args)
1047-
1048-
1049-class UbuntuSSOProxy(BaseService):
1050+class UbuntuSSOProxy(UbuntuSSOProxyBase):
1051 """Object that exposes the diff referenceable objects."""
1052
1053- services = {
1054- 'sso_login': SSOLoginProxy,
1055- 'cred_manager': CredentialsManagementProxy,
1056- }
1057-
1058 name = SSO_SERVICE_NAME
1059
1060 @property
1061 def port(self):
1062- """Get the port of the remote service."""
1063+ """Get the port on which the SSO pb is running."""
1064 return get_sso_pb_port()
1065
1066 @property
1067 def cmdline(self):
1068- """Get the command line to run the service executable."""
1069- return get_activation_cmdline(UbuntuSSOProxy.name)
1070-
1071-
1072-# ============================== client classes ==============================
1073-
1074-
1075-class SSOLoginClient(RemoteClient):
1076- """Client that can perform calls to the remote SSOLogin object."""
1077-
1078- call_remote_functions = SSOLoginProxy.remote_calls
1079- signal_handlers = [
1080- 'CaptchaGenerated',
1081- 'CaptchaGenerationError',
1082- 'UserRegistered',
1083- 'UserRegistrationError',
1084- 'LoggedIn',
1085- 'LoginError',
1086- 'UserNotValidated',
1087- 'EmailValidated',
1088- 'EmailValidationError',
1089- 'PasswordResetTokenSent',
1090- 'PasswordResetError',
1091- 'PasswordChanged',
1092- 'PasswordChangeError',
1093- ]
1094-
1095-
1096-class CredentialsManagementClient(RemoteClient):
1097- """Client that can perform calls to the remote CredManagement object."""
1098-
1099- call_remote_functions = CredentialsManagementProxy.remote_calls
1100- signal_handlers = [
1101- 'AuthorizationDenied',
1102- 'CredentialsFound',
1103- 'CredentialsNotFound',
1104- 'CredentialsCleared',
1105- 'CredentialsStored',
1106- 'CredentialsError',
1107- ]
1108+ """Get the command line to activate an executable."""
1109+ return get_activation_cmdline(self.name)
1110
1111
1112 class UbuntuSSOClient(BaseClient):
1113 """Base client that provides remote access to the sso API."""
1114
1115+ name = SSO_SERVICE_NAME
1116+
1117 clients = {
1118 'sso_login': SSOLoginClient,
1119 'cred_manager': CredentialsManagementClient,
1120@@ -405,36 +126,3 @@
1121 result = UbuntuSSOClient()
1122 yield result.connect()
1123 defer.returnValue(result)
1124-
1125-
1126-def add_timeout(interval, callback, *args, **kwargs):
1127- """Add a timeout callback as a task."""
1128- time_out_task = LoopingCall(callback, *args, **kwargs)
1129- time_out_task.start(interval / 1000, now=False)
1130-
1131-
1132-timeout_func = add_timeout
1133-start_setup = lambda *a, **kw: None
1134-
1135-# the reactor does have run and stop methods
1136-# pylint: disable=E1101
1137-
1138-
1139-def shutdown_func():
1140- """Stop the reactor."""
1141- from twisted.internet import reactor
1142- reactor.stop()
1143-
1144-
1145-def finish_setup(result, loop):
1146- """Stop the reactor if a failure ocurred."""
1147- if isinstance(result, Failure):
1148- shutdown_func()
1149-
1150-
1151-def main():
1152- """Run the specific mainloop."""
1153- from twisted.internet import reactor
1154- reactor.run()
1155-
1156-# pylint: enable=E1101
1157
1158=== modified file 'ubuntu_sso/networkstate/__init__.py'
1159--- ubuntu_sso/networkstate/__init__.py 2012-04-09 17:38:24 +0000
1160+++ ubuntu_sso/networkstate/__init__.py 2012-04-23 16:23:20 +0000
1161@@ -1,5 +1,4 @@
1162 # -*- coding: utf-8 -*-
1163-# Author: Manuel de la Pena <manuel@canonical.com>
1164 #
1165 # Copyright 2011-2012 Canonical Ltd.
1166 #
1167@@ -47,15 +46,16 @@
1168
1169 if sys.platform == 'win32':
1170 from ubuntu_sso.networkstate import windows
1171- NetworkManagerState = windows.NetworkManagerState
1172- ONLINE = windows.ONLINE
1173- OFFLINE = windows.OFFLINE
1174- UNKNOWN = windows.UNKNOWN
1175- is_machine_connected = windows.is_machine_connected
1176+ networksource = windows
1177+elif sys.platform == 'darwin':
1178+ from ubuntu_sso.networkstate import darwin
1179+ networksource = darwin
1180 else:
1181 from ubuntu_sso.networkstate import linux
1182- NetworkManagerState = linux.NetworkManagerState
1183- ONLINE = linux.ONLINE
1184- OFFLINE = linux.OFFLINE
1185- UNKNOWN = linux.UNKNOWN
1186- is_machine_connected = linux.is_machine_connected
1187+ networksource = linux
1188+
1189+NetworkManagerState = networksource.NetworkManagerState
1190+ONLINE = networksource.ONLINE
1191+OFFLINE = networksource.OFFLINE
1192+UNKNOWN = networksource.UNKNOWN
1193+is_machine_connected = networksource.is_machine_connected
1194
1195=== added file 'ubuntu_sso/networkstate/darwin.py'
1196--- ubuntu_sso/networkstate/darwin.py 1970-01-01 00:00:00 +0000
1197+++ ubuntu_sso/networkstate/darwin.py 2012-04-23 16:23:20 +0000
1198@@ -0,0 +1,95 @@
1199+# -*- coding: utf-8 -*-
1200+#
1201+# Copyright 2012 Canonical Ltd.
1202+#
1203+# This program is free software: you can redistribute it and/or modify it
1204+# under the terms of the GNU General Public License version 3, as published
1205+# by the Free Software Foundation.
1206+#
1207+# This program is distributed in the hope that it will be useful, but
1208+# WITHOUT ANY WARRANTY; without even the implied warranties of
1209+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1210+# PURPOSE. See the GNU General Public License for more details.
1211+#
1212+# You should have received a copy of the GNU General Public License along
1213+# with this program. If not, see <http://www.gnu.org/licenses/>.
1214+"""Implementation of network state detection."""
1215+
1216+from twisted.internet import defer
1217+
1218+from ubuntu_sso.networkstate import NetworkFailException
1219+from ubuntu_sso.networkstate.networkstates import (
1220+ ONLINE, OFFLINE, UNKNOWN,
1221+
1222+ NM_STATE_CONNECTING_LIST,
1223+ NM_STATE_CONNECTED_LIST,
1224+ NM_STATE_DISCONNECTED_LIST,
1225+ )
1226+from ubuntu_sso.networkstate.networkstates import NM_STATE_CONNECTED_GLOBAL
1227+from ubuntu_sso.logger import setup_logging
1228+logger = setup_logging("ubuntu_sso.networkstate")
1229+
1230+
1231+class NetworkManagerState(object):
1232+ """All likely to change very soon."""
1233+
1234+ def __init__(self, result_cb):
1235+ """Initialize this instance with a result and error callbacks."""
1236+ self.result_cb = result_cb
1237+ self.state_signal = None
1238+
1239+ def call_result_cb(self, state):
1240+ """Return the state thru the result callback."""
1241+ self.result_cb(state)
1242+
1243+ def got_state(self, state):
1244+ """Not actually called by DBus when the state is retrieved from NM."""
1245+ if state in NM_STATE_CONNECTED_LIST:
1246+ self.call_result_cb(ONLINE)
1247+ elif state in NM_STATE_CONNECTING_LIST:
1248+ logger.debug("Currently connecting, waiting for signal")
1249+ else:
1250+ self.call_result_cb(OFFLINE)
1251+
1252+ def got_error(self, error):
1253+ """Not actually called by DBus when the state is retrieved from NM."""
1254+ logger.error("Error contacting NetworkManager: %s" % \
1255+ str(error))
1256+ self.call_result_cb(UNKNOWN)
1257+
1258+ def state_changed(self, state):
1259+ """Called when a signal is emmited by Network Manager."""
1260+ if int(state) in NM_STATE_CONNECTED_LIST:
1261+ self.call_result_cb(ONLINE)
1262+ elif int(state) in NM_STATE_DISCONNECTED_LIST:
1263+ self.call_result_cb(OFFLINE)
1264+ else:
1265+ logger.debug("Not yet connected: continuing to wait")
1266+
1267+ def find_online_state(self):
1268+ """Get the network state and return it thru the set callback."""
1269+ try:
1270+ self.state_changed(NM_STATE_CONNECTED_GLOBAL)
1271+ except Exception, e: # pylint: disable=W0703
1272+ self.got_error(e)
1273+
1274+
1275+def is_machine_connected():
1276+ """Return a deferred that when fired, returns if the machine is online."""
1277+ d = defer.Deferred()
1278+
1279+ def got_state(state):
1280+ """The state was retrieved from the Network Manager."""
1281+ result = int(state) in NM_STATE_CONNECTED_LIST
1282+ d.callback(result)
1283+
1284+ # pylint: disable=W0702, W0703
1285+ try:
1286+ network = NetworkManagerState(got_state)
1287+ network.find_online_state()
1288+ except Exception, e:
1289+ logger.exception('is_machine_connected failed with:')
1290+ d.errback(NetworkFailException(e))
1291+ # pylint: enable=W0702, W0703
1292+
1293+ return d
1294
1295=== modified file 'ubuntu_sso/networkstate/linux.py'
1296--- ubuntu_sso/networkstate/linux.py 2012-04-09 17:38:24 +0000
1297+++ ubuntu_sso/networkstate/linux.py 2012-04-23 16:23:20 +0000
1298@@ -1,9 +1,5 @@
1299 # -*- coding: utf-8 -*-
1300 #
1301-# networkstate - detect the current state of the network
1302-#
1303-# Author: Alejandro J. Cura <alecu@canonical.com>
1304-#
1305 # Copyright 2010-2012 Canonical Ltd.
1306 #
1307 # This program is free software: you can redistribute it and/or modify it
1308@@ -37,44 +33,16 @@
1309 from twisted.internet import defer
1310
1311 from ubuntu_sso.networkstate import NetworkFailException
1312+from ubuntu_sso.networkstate.networkstates import (
1313+ ONLINE, OFFLINE, UNKNOWN,
1314+
1315+ NM_STATE_CONNECTING_LIST,
1316+ NM_STATE_CONNECTED_LIST,
1317+ NM_STATE_DISCONNECTED_LIST,
1318+ )
1319 from ubuntu_sso.logger import setup_logging
1320 logger = setup_logging("ubuntu_sso.networkstate")
1321
1322-# Values returned by the callback
1323-ONLINE, OFFLINE, UNKNOWN = object(), object(), object()
1324-
1325-NM_STATE_NAMES = {
1326- ONLINE: "online",
1327- OFFLINE: "offline",
1328- UNKNOWN: "unknown",
1329-}
1330-
1331-# Internal NetworkManager State constants
1332-NM_STATE_UNKNOWN = 0
1333-NM_STATE_UNKNOWN_LIST = [NM_STATE_UNKNOWN]
1334-NM_STATE_ASLEEP_OLD = 1
1335-NM_STATE_ASLEEP = 10
1336-NM_STATE_ASLEEP_LIST = [NM_STATE_ASLEEP_OLD,
1337- NM_STATE_ASLEEP]
1338-NM_STATE_CONNECTING_OLD = 2
1339-NM_STATE_CONNECTING = 40
1340-NM_STATE_CONNECTING_LIST = [NM_STATE_CONNECTING_OLD,
1341- NM_STATE_CONNECTING]
1342-NM_STATE_CONNECTED_OLD = 3
1343-NM_STATE_CONNECTED_LOCAL = 50
1344-NM_STATE_CONNECTED_SITE = 60
1345-NM_STATE_CONNECTED_GLOBAL = 70
1346-# Specifically don't include local and site, as they won't let us get to server
1347-NM_STATE_CONNECTED_LIST = [NM_STATE_CONNECTED_OLD,
1348- NM_STATE_CONNECTED_GLOBAL]
1349-NM_STATE_DISCONNECTED_OLD = 4
1350-NM_STATE_DISCONNECTED = 20
1351-# For us, local and site connections are the same as diconnected
1352-NM_STATE_DISCONNECTED_LIST = [NM_STATE_DISCONNECTED_OLD,
1353- NM_STATE_DISCONNECTED,
1354- NM_STATE_CONNECTED_LOCAL,
1355- NM_STATE_CONNECTED_SITE]
1356-
1357 NM_DBUS_INTERFACE = "org.freedesktop.NetworkManager"
1358 NM_DBUS_OBJECTPATH = "/org/freedesktop/NetworkManager"
1359 DBUS_UNKNOWN_SERVICE = "org.freedesktop.DBus.Error.ServiceUnknown"
1360
1361=== added file 'ubuntu_sso/networkstate/networkstates.py'
1362--- ubuntu_sso/networkstate/networkstates.py 1970-01-01 00:00:00 +0000
1363+++ ubuntu_sso/networkstate/networkstates.py 2012-04-23 16:23:20 +0000
1364@@ -0,0 +1,51 @@
1365+# -*- coding: utf-8 -*-
1366+#
1367+# Copyright 2012 Canonical Ltd.
1368+#
1369+# This program is free software: you can redistribute it and/or modify it
1370+# under the terms of the GNU General Public License version 3, as published
1371+# by the Free Software Foundation.
1372+#
1373+# This program is distributed in the hope that it will be useful, but
1374+# WITHOUT ANY WARRANTY; without even the implied warranties of
1375+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1376+# PURPOSE. See the GNU General Public License for more details.
1377+#
1378+# You should have received a copy of the GNU General Public License along
1379+# with this program. If not, see <http://www.gnu.org/licenses/>.
1380+"""Network states."""
1381+
1382+# Values returned by the callback
1383+ONLINE, OFFLINE, UNKNOWN = object(), object(), object()
1384+
1385+NM_STATE_NAMES = {
1386+ ONLINE: "online",
1387+ OFFLINE: "offline",
1388+ UNKNOWN: "unknown",
1389+}
1390+
1391+# Internal NetworkManager State constants
1392+NM_STATE_UNKNOWN = 0
1393+NM_STATE_UNKNOWN_LIST = [NM_STATE_UNKNOWN]
1394+NM_STATE_ASLEEP_OLD = 1
1395+NM_STATE_ASLEEP = 10
1396+NM_STATE_ASLEEP_LIST = [NM_STATE_ASLEEP_OLD,
1397+ NM_STATE_ASLEEP]
1398+NM_STATE_CONNECTING_OLD = 2
1399+NM_STATE_CONNECTING = 40
1400+NM_STATE_CONNECTING_LIST = [NM_STATE_CONNECTING_OLD,
1401+ NM_STATE_CONNECTING]
1402+NM_STATE_CONNECTED_OLD = 3
1403+NM_STATE_CONNECTED_LOCAL = 50
1404+NM_STATE_CONNECTED_SITE = 60
1405+NM_STATE_CONNECTED_GLOBAL = 70
1406+# Specifically don't include local and site, as they won't let us get to server
1407+NM_STATE_CONNECTED_LIST = [NM_STATE_CONNECTED_OLD,
1408+ NM_STATE_CONNECTED_GLOBAL]
1409+NM_STATE_DISCONNECTED_OLD = 4
1410+NM_STATE_DISCONNECTED = 20
1411+# For us, local and site connections are the same as diconnected
1412+NM_STATE_DISCONNECTED_LIST = [NM_STATE_DISCONNECTED_OLD,
1413+ NM_STATE_DISCONNECTED,
1414+ NM_STATE_CONNECTED_LOCAL,
1415+ NM_STATE_CONNECTED_SITE]
1416
1417=== modified file 'ubuntu_sso/networkstate/tests/test_linux.py'
1418--- ubuntu_sso/networkstate/tests/test_linux.py 2012-04-09 17:38:24 +0000
1419+++ ubuntu_sso/networkstate/tests/test_linux.py 2012-04-23 16:23:20 +0000
1420@@ -1,9 +1,5 @@
1421 # -*- coding: utf-8 -*-
1422 #
1423-# test_networkstate - tests for ubuntu_sso.networkstate
1424-#
1425-# Author: Alejandro J. Cura <alecu@canonical.com>
1426-#
1427 # Copyright 2010-2012 Canonical Ltd.
1428 #
1429 # This program is free software: you can redistribute it and/or modify it
1430@@ -42,13 +38,10 @@
1431 linux,
1432 NetworkFailException,
1433 NetworkManagerState,
1434+)
1435+
1436+from ubuntu_sso.networkstate.networkstates import (
1437 ONLINE, OFFLINE, UNKNOWN,
1438-)
1439-
1440-from ubuntu_sso.networkstate.linux import (is_machine_connected,
1441- DBUS_UNKNOWN_SERVICE,
1442- NM_DBUS_INTERFACE,
1443- NM_DBUS_OBJECTPATH,
1444 NM_STATE_ASLEEP,
1445 NM_STATE_ASLEEP_OLD,
1446 NM_STATE_CONNECTING,
1447@@ -62,6 +55,12 @@
1448 NM_STATE_UNKNOWN,
1449 )
1450
1451+from ubuntu_sso.networkstate.linux import (is_machine_connected,
1452+ DBUS_UNKNOWN_SERVICE,
1453+ NM_DBUS_INTERFACE,
1454+ NM_DBUS_OBJECTPATH,
1455+)
1456+
1457
1458 class TestException(Exception):
1459 """An exception to test error conditions."""
1460
1461=== modified file 'ubuntu_sso/qt/__init__.py'
1462--- ubuntu_sso/qt/__init__.py 2012-04-09 17:38:24 +0000
1463+++ ubuntu_sso/qt/__init__.py 2012-04-23 16:23:20 +0000
1464@@ -28,6 +28,7 @@
1465 # files in the program, then also delete it here.
1466 """The Qt graphical interface for the Ubuntu Single Sign On Client."""
1467
1468+import sys
1469 import collections
1470
1471 from PyQt4 import QtGui, QtCore
1472@@ -46,6 +47,12 @@
1473 TITLE_STYLE = u'<span style="font-size:xx-large;font-weight:bold;">%s</span>'
1474 WINDOW_TITLE = 'Ubuntu Single Sign On'
1475
1476+# TODO: There is a pixel discrepancy between Qt on Linux and Windows
1477+# and Mac OS X. On Mac OS X, one test fails with the height being
1478+# off. For now, we're forcing a different UI height on darwin.
1479+if sys.platform == 'darwin':
1480+ PREFERED_UI_SIZE['height'] = 533
1481+
1482
1483 # Based on the gtk implementation
1484 def build_general_error_message(errordict):
1485
1486=== modified file 'ubuntu_sso/qt/main/__init__.py'
1487--- ubuntu_sso/qt/main/__init__.py 2012-04-09 17:38:24 +0000
1488+++ ubuntu_sso/qt/main/__init__.py 2012-04-23 16:23:20 +0000
1489@@ -41,7 +41,7 @@
1490
1491
1492 # Invalid name "source", pylint: disable=C0103
1493-if sys.platform == 'win32':
1494+if sys.platform in ('win32', 'darwin'):
1495 from ubuntu_sso.qt.main import windows
1496 source = windows
1497 else:
1498
1499=== modified file 'ubuntu_sso/utils/__init__.py'
1500--- ubuntu_sso/utils/__init__.py 2012-04-09 17:38:24 +0000
1501+++ ubuntu_sso/utils/__init__.py 2012-04-23 16:23:20 +0000
1502@@ -47,8 +47,12 @@
1503 BIN_SUFFIX = 'bin'
1504 DATA_SUFFIX = 'data'
1505
1506-if sys.platform == "win32":
1507- from ubuntu_sso.utils import windows as source
1508+if sys.platform in ('win32', 'darwin'):
1509+ from ubuntu_sso.utils import qtwisted as source
1510+ qss_map = {'win32': ':/windows.qss',
1511+ 'darwin': ':/darwin.qss',
1512+ }
1513+ source.PLATFORM_QSS = qss_map[sys.platform]
1514 else:
1515 from ubuntu_sso.utils import linux as source
1516 PLATFORM_QSS = source.PLATFORM_QSS
1517
1518=== renamed file 'ubuntu_sso/utils/windows.py' => 'ubuntu_sso/utils/qtwisted.py'
1519--- ubuntu_sso/utils/windows.py 2012-04-09 17:38:24 +0000
1520+++ ubuntu_sso/utils/qtwisted.py 2012-04-23 16:23:20 +0000
1521@@ -26,9 +26,7 @@
1522 # do not wish to do so, delete this exception statement from your
1523 # version. If you delete this exception statement from all source
1524 # files in the program, then also delete it here.
1525-"""Platform specific constants and functions (for Windows)."""
1526-
1527-PLATFORM_QSS = ":/windows.qss"
1528+"""Platform specific constants and functions (for Qt/Twisted)."""
1529
1530 from twisted.internet import defer
1531 from PyQt4.QtCore import QThread, QCoreApplication
1532
1533=== renamed file 'ubuntu_sso/utils/tests/test_windows.py' => 'ubuntu_sso/utils/tests/test_qtwisted.py'
1534--- ubuntu_sso/utils/tests/test_windows.py 2012-04-09 17:38:24 +0000
1535+++ ubuntu_sso/utils/tests/test_qtwisted.py 2012-04-23 16:23:20 +0000
1536@@ -33,7 +33,7 @@
1537 from twisted.internet import defer
1538 from twisted.trial.unittest import TestCase
1539
1540-from ubuntu_sso.utils.windows import qtDeferToThread
1541+from ubuntu_sso.utils.qtwisted import qtDeferToThread
1542
1543
1544 class FakeException(Exception):

Subscribers

People subscribed via source and target branches