Merge lp:~leonardr/lazr.restfulclient/system-wide-consumer into lp:lazr.restfulclient

Proposed by Leonard Richardson
Status: Merged
Merged at revision: 110
Proposed branch: lp:~leonardr/lazr.restfulclient/system-wide-consumer
Merge into: lp:lazr.restfulclient
Diff against target: 217 lines (+157/-1)
3 files modified
src/lazr/restfulclient/NEWS.txt (+5/-1)
src/lazr/restfulclient/authorize/oauth.py (+46/-0)
src/lazr/restfulclient/tests/test_oauth.py (+106/-0)
To merge this branch: bzr merge lp:~leonardr/lazr.restfulclient/system-wide-consumer
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) code Approve
Review via email: mp+39552@code.launchpad.net

Description of the change

This branch introduces the SystemWideConsumer class. With a normal OAuth consumer, you specify the consumer key (the name of your application). With the SystemWideConsumer, the consumer key is automatically derived from your platform (eg. "Ubuntu") and the hostname of your computer. You still specify an "application name", but it's only present in the User-Agent. It's not used in OAuth.

The purpose of this mighty consumer is to integrate an entire desktop with a web service, rather than having to integrate each application individually. The KEY_FORMAT happens to be the format that Launchpad recognizes as a system-wide consumer key, but the code is generic--it (or a simple subclass) could work for any lazr.restful service, whether or not it specifically recognizes system-wide keys.

If KEY_FORMAT bothers you because of its Launchpad-specificness I could make it a constructor argument and move KEY_FORMAT itself to launchpadlib.

To post a comment you must log in.
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

Hi Leonard,

This is a nice branch. I just have one concern listed below.

-Edwin

>=== modified file 'src/lazr/restfulclient/authorize/oauth.py'
>--- src/lazr/restfulclient/authorize/oauth.py 2010-10-28 15:49:09 +0000
>+++ src/lazr/restfulclient/authorize/oauth.py 2010-10-28 20:57:14 +0000
>@@ -73,6 +76,49 @@
> self.context = context
>
>
>+class SystemWideConsumer(Consumer):
>+ """A consumer associated with the logged-in user rather than an app.
>+
>+ This can be used to share a single OAuth token among multiple
>+ desktop applications. The OAuth consumer key will be derived from
>+ system information (platform and hostname).
>+ """
>+ KEY_FORMAT = "System-wide: %s (%s)"
>+
>+ def __init__(self, application_name, secret=''):
>+ """Constructor.
>+
>+ :param application_name: An application name. This will be
>+ used in the User-Agent header.
>+ :param secret: The OAuth consumer secret. Don't use this. It's
>+ a misfeature, and lazr.restful doesn't expect it.
>+ """
>+ super(SystemWideConsumer, self).__init__(
>+ self.consumer_key, secret, application_name)
>+
>+ @property
>+ def consumer_key(self):
>+ """The system-wide OAuth consumer key for this computer.
>+
>+ This key identifies the platform and the computer's
>+ hostname. It does not identify the active user.
>+ """
>+ try:
>+ distname, version, release_id = platform.linux_distribution()
>+ except Exception, e:
>+ # This can happen in pre-2.6 versions of Python.
>+ try:
>+ distname, version, release_id = platform.dist()

There appears to be an annoying bug in some versions of python where platform.dist() will randomly get its data from either /etc/debian_version or /etc/lsb-release. Since lsb-release has the more accurate info, it might be worthwhile to use "/usr/bin/lsb_release -si" when that executable exists.

http://bugs.python.org/issue9514

>+ except Exception, e:
>+ # This should never happen--non-Linux platforms return
>+ # empty strings from linux_distribution() or
>+ # dist()--but just in case.
>+ distname = ''
>+ if distname == '':
>+ distname = platform.system() # (eg. "Windows")
>+ return self.KEY_FORMAT % (distname, socket.gethostname())
>+
>+

review: Approve (code)
Revision history for this message
Leonard Richardson (leonardr) wrote :

I've gotten rid of the use of dist(). We'll now fall back directly to system().

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/lazr/restfulclient/NEWS.txt'
--- src/lazr/restfulclient/NEWS.txt 2010-10-27 14:18:31 +0000
+++ src/lazr/restfulclient/NEWS.txt 2010-10-28 17:46:02 +0000
@@ -2,13 +2,17 @@
2NEWS for lazr.restfulclient2NEWS for lazr.restfulclient
3===========================3===========================
44
50.11.0 (2010-10-27)50.11.0 (2010-10-28)
6===================6===================
77
8 - Make it possibly to specify an "application name" separate from the8 - Make it possibly to specify an "application name" separate from the
9 OAuth consumer key. If present, the application name is used in the9 OAuth consumer key. If present, the application name is used in the
10 User-Agent header; otherwise, the OAuth consumer key is used.10 User-Agent header; otherwise, the OAuth consumer key is used.
1111
12 - Add a "system-wide consumer" which can be used to authorize a
13 user's entire account to use a web service, rather than doing it
14 one application at a time.
15
120.10.0 (2010-08-12)160.10.0 (2010-08-12)
13===================17===================
1418
1519
=== modified file 'src/lazr/restfulclient/authorize/oauth.py'
--- src/lazr/restfulclient/authorize/oauth.py 2010-10-28 15:49:09 +0000
+++ src/lazr/restfulclient/authorize/oauth.py 2010-10-28 17:46:02 +0000
@@ -21,7 +21,9 @@
2121
22from ConfigParser import SafeConfigParser22from ConfigParser import SafeConfigParser
23import os23import os
24import platform
24import stat25import stat
26import socket
25# Work around relative import behavior. The below is equivalent to27# Work around relative import behavior. The below is equivalent to
26# from oauth import oauth28# from oauth import oauth
27oauth = __import__('oauth.oauth', {}).oauth29oauth = __import__('oauth.oauth', {}).oauth
@@ -37,6 +39,7 @@
37 'AccessToken',39 'AccessToken',
38 'Consumer',40 'Consumer',
39 'OAuthAuthorizer',41 'OAuthAuthorizer',
42 'SystemWideConsumer',
40 ]43 ]
4144
4245
@@ -73,6 +76,49 @@
73 self.context = context76 self.context = context
7477
7578
79class SystemWideConsumer(Consumer):
80 """A consumer associated with the logged-in user rather than an app.
81
82 This can be used to share a single OAuth token among multiple
83 desktop applications. The OAuth consumer key will be derived from
84 system information (platform and hostname).
85 """
86 KEY_FORMAT = "System-wide: %s (%s)"
87
88 def __init__(self, application_name, secret=''):
89 """Constructor.
90
91 :param application_name: An application name. This will be
92 used in the User-Agent header.
93 :param secret: The OAuth consumer secret. Don't use this. It's
94 a misfeature, and lazr.restful doesn't expect it.
95 """
96 super(SystemWideConsumer, self).__init__(
97 self.consumer_key, secret, application_name)
98
99 @property
100 def consumer_key(self):
101 """The system-wide OAuth consumer key for this computer.
102
103 This key identifies the platform and the computer's
104 hostname. It does not identify the active user.
105 """
106 try:
107 distname, version, release_id = platform.linux_distribution()
108 except Exception, e:
109 # This can happen in pre-2.6 versions of Python.
110 try:
111 distname, version, release_id = platform.dist()
112 except Exception, e:
113 # This should never happen--non-Linux platforms return
114 # empty strings from linux_distribution() or
115 # dist()--but just in case.
116 distname = ''
117 if distname == '':
118 distname = platform.system() # (eg. "Windows")
119 return self.KEY_FORMAT % (distname, socket.gethostname())
120
121
76class OAuthAuthorizer(HttpAuthorizer):122class OAuthAuthorizer(HttpAuthorizer):
77 """A client that signs every outgoing request with OAuth credentials."""123 """A client that signs every outgoing request with OAuth credentials."""
78124
79125
=== modified file 'src/lazr/restfulclient/tests/test_oauth.py'
--- src/lazr/restfulclient/tests/test_oauth.py 2010-10-28 15:49:09 +0000
+++ src/lazr/restfulclient/tests/test_oauth.py 2010-10-28 17:46:02 +0000
@@ -27,6 +27,7 @@
27import tempfile27import tempfile
28import unittest28import unittest
2929
30from lazr.restfulclient.authorize import oauth
30from lazr.restfulclient.authorize.oauth import (31from lazr.restfulclient.authorize.oauth import (
31 AccessToken,32 AccessToken,
32 Consumer,33 Consumer,
@@ -47,6 +48,111 @@
47 consumer = Consumer("key", "secret")48 consumer = Consumer("key", "secret")
48 self.assertEquals(consumer.application_name, None)49 self.assertEquals(consumer.application_name, None)
4950
51
52class TestSystemWideConsumer(unittest.TestCase):
53
54 def setUp(self):
55 """Save the original 'platform' and 'socket' modules.
56
57 The tests will be replacing them with dummies.
58 """
59 self.original_platform = oauth.platform
60 self.original_socket = oauth.socket
61
62 def tearDown(self):
63 """Replace the original 'platform' and 'socket' modules."""
64 oauth.platform = self.original_platform
65 oauth.socket = self.original_socket
66
67 def _set_hostname(self, hostname):
68 """Changes the socket module to simulate the given hostname."""
69 class DummySocket:
70 def gethostname(self):
71 return hostname
72 oauth.socket = DummySocket()
73
74 def _set_platform(self, linux_distribution, system, dist=None):
75 """Changes the platform module to simulate different behavior.
76
77 :param linux_distribution: A tuple to be returned by
78 linux_distribution(), or a callable that implements
79 linux_distribution().
80 :param system: A string to be returned by system()
81 :param dist: A callable that implements dist(). If this is
82 None, dist() will behave exactly the same as
83 linux_distribution().
84 """
85
86 if isinstance(linux_distribution, tuple):
87 def get_linux_distribution(self):
88 return linux_distribution
89 else:
90 # The caller provided their own implementation of
91 # linux_distribution().
92 get_linux_distribution = linux_distribution
93
94 if dist is None:
95 # The caller declined to provide an implementation of dist().
96 # Make it act like linux_distribution().
97 get_dist = linux_distribution
98 else:
99 get_dist = dist
100
101 class DummyPlatform:
102 linux_distribution = get_linux_distribution
103 dist = get_dist
104 def system(self):
105 return system
106 oauth.platform = DummyPlatform()
107
108 def _broken(self):
109 """Raises an exception."""
110 raise Exception("Oh noes!")
111
112 def test_useful_linux_distribution(self):
113 # If platform.linux_distribution returns a tuple of useful
114 # strings, as it does on Ubuntu, we'll use the first string
115 # for the system type.
116 self._set_platform(('Fooix', 'String2', 'String3'), 'FooOS')
117 self._set_hostname("foo")
118 consumer = oauth.SystemWideConsumer("app name")
119 self.assertEquals(
120 consumer.key, 'System-wide: Fooix (foo)')
121
122 def test_empty_linux_distribution(self):
123 # If platform.linux_distribution returns a tuple of empty
124 # strings, as it does on Windows and Mac OS X, we fall back to
125 # the result of platform.system().
126 self._set_platform(('', '', ''), 'BarOS')
127 self._set_hostname("bar")
128 consumer = oauth.SystemWideConsumer("app name")
129 self.assertEquals(
130 consumer.key, 'System-wide: BarOS (bar)')
131
132 def test_broken_linux_distribution(self):
133 # If platform.linux_distribution raises an exception (which
134 # can happen with older versions of Python), we fall back to
135 # the result of platform.dist().
136 def dist(self):
137 return ('Bazix', 'String2', 'String3')
138 self._set_platform(self._broken, 'BazOS', dist)
139 self._set_hostname("baz")
140 consumer = oauth.SystemWideConsumer("app name")
141 self.assertEquals(
142 consumer.key, 'System-wide: Bazix (baz)')
143
144 def test_broken_linux_distribution_and_dist(self):
145 # If both platform.linux_distribution and platform.dist raise
146 # exceptions (which should never actually happen)
147 # we fall back to the result of platform.system().
148 self._set_platform(self._broken, 'QuuxOS', self._broken)
149 self._set_hostname("quux")
150 consumer = oauth.SystemWideConsumer("app name")
151 self.assertEquals(
152 consumer.key, 'System-wide: QuuxOS (quux)')
153
154
155
50class TestOAuthAuthorizer(unittest.TestCase):156class TestOAuthAuthorizer(unittest.TestCase):
51 """Test for the OAuth Authorizer."""157 """Test for the OAuth Authorizer."""
52158

Subscribers

People subscribed via source and target branches