Merge lp:~mandel/desktopcouch/add_windows_keyring into lp:desktopcouch

Proposed by Manuel de la Peña
Status: Merged
Approved by: Eric Casteleijn
Approved revision: 223
Merged at revision: 231
Proposed branch: lp:~mandel/desktopcouch/add_windows_keyring
Merge into: lp:desktopcouch
Diff against target: 381 lines (+366/-0)
3 files modified
desktopcouch/application/platform/windows/keyring.py (+138/-0)
desktopcouch/application/platform/windows/tests/__init__.py (+1/-0)
desktopcouch/application/platform/windows/tests/test_keyring.py (+227/-0)
To merge this branch: bzr merge lp:~mandel/desktopcouch/add_windows_keyring
Reviewer Review Type Date Requested Status
Eric Casteleijn (community) Approve
Chad Miller (community) Approve
Review via email: mp+41874@code.launchpad.net

Commit message

Provides the windows implementation of the keyring that can be used in windows. Data is stored in the Registry and uses the DPAPI so that it is not viewable by a user that has not been logged as the user that owns the registry (apps running in an other user space)

Description of the change

Provides the windows implementation of the keyring that can be used in windows. Data is stored in the Registry and uses the DPAPI so that it is not viewable by a user that has not been logged as the user that owns the registry (apps running in an other user space)

To post a comment you must log in.
Revision history for this message
Chad Miller (cmiller) wrote :

38 + def __init__(self, make_random_string_fn=make_random_string,
39 + registry=None, crypto=None):
40 +

If you inherit from object, you much call object's __init__().
> super(Keyring, self).__init__()

73 + for index in count():

Very sexy. Thx.

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

Called missing parent constructor.

Revision history for this message
Chad Miller (cmiller) :
review: Approve
Revision history for this message
Eric Casteleijn (thisfred) wrote :

Looking good, let's land it!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'desktopcouch/application/platform/windows/keyring.py'
2--- desktopcouch/application/platform/windows/keyring.py 1970-01-01 00:00:00 +0000
3+++ desktopcouch/application/platform/windows/keyring.py 2010-11-26 14:26:35 +0000
4@@ -0,0 +1,138 @@
5+# Copyright 2009 Canonical Ltd.
6+#
7+# This file is part of desktopcouch.
8+#
9+# desktopcouch is free software: you can redistribute it and/or modify
10+# it under the terms of the GNU Lesser General Public License version 3
11+# as published by the Free Software Foundation.
12+#
13+# desktopcouch is distributed in the hope that it will be useful,
14+# but WITHOUT ANY WARRANTY; without even the implied warranty of
15+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+# GNU Lesser General Public License for more details.
17+#
18+# You should have received a copy of the GNU Lesser General Public License
19+# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.
20+#
21+# Author: Manuel de la Pena <manuel.delapena@canonical.com>
22+"""Keyring implementation on windows."""
23+
24+import base64
25+
26+from itertools import count
27+
28+from desktopcouch.application.util import make_random_string
29+
30+
31+class Keyring(object):
32+ """Implementation to be used on windows.
33+
34+ Provides the implementation of the keyring operations on windows
35+ using the crypto lib and the registry key to store the info.
36+ """
37+
38+ def __init__(self, make_random_string_fn=make_random_string,
39+ registry=None, crypto=None):
40+ super(Keyring, self).__init__()
41+ # ignore pylint, we might not be on windows
42+ # pylint: disable=F0401
43+ if registry is None:
44+ import _winreg
45+ registry = _winreg
46+
47+ if crypto is None:
48+ import win32crypt
49+ crypto = win32crypt
50+ # pylint: enable=F0401
51+
52+ self.make_random_string = make_random_string_fn
53+ self.registry = registry
54+ self.crypto = crypto
55+
56+ def _registry_value_exists(self, value):
57+ """Return if the required key exists in the system."""
58+ # ignore the fact that we do not specify the expcetion, we do
59+ # this so that the tests can run on linux.
60+ # pylint: disable=W0702
61+ # try to open it, if not, return false
62+ try:
63+ access_rights = self.registry.KEY_ALL_ACCESS
64+ canonical = self.registry.OpenKey(self.registry.HKEY_CURRENT_USER,
65+ 'Canonical', 0, access_rights)
66+ keyrings = self.registry.OpenKey(canonical,
67+ 'Keyrings', 0, access_rights)
68+ default = self.registry.OpenKey(keyrings,
69+ 'Default', 0, access_rights)
70+ desktopcouch = self.registry.OpenKey(default,
71+ 'Desktopcouch', 0, access_rights)
72+ # enum until we get a exception
73+ for index in count():
74+ try:
75+ info = self.registry.EnumValue(
76+ desktopcouch, index)
77+ if info[0] == value:
78+ return True
79+ except:
80+ return False
81+ except:
82+ return False
83+ # pylint: enable=W0702
84+
85+ def _open_registry_key(self):
86+ """Open the required registry key"""
87+ # we need to open the key from the keyring location in the
88+ # registry, the easiest way is to open step by step each key
89+ # using CreateKey since it will create the key if required
90+ canonical_key = self.registry.CreateKey(
91+ self.registry.HKEY_CURRENT_USER,
92+ 'Canonical')
93+ keyring_key = self.registry.CreateKey(
94+ canonical_key, 'Keyrings')
95+ default_key = self.registry.CreateKey(
96+ keyring_key, 'Default')
97+ return self.registry.CreateKey(
98+ default_key, 'Desktopcouch')
99+
100+ def get_user_name_password(self):
101+ """Return the user name and passwd used to access desktopcouch."""
102+ admin_username = None
103+ admin_password = None
104+ data_exists = self._registry_value_exists('basic')
105+ key = self._open_registry_key()
106+ if data_exists:
107+ # read and return it
108+ secret = self.registry.QueryValueEx(key, 'basic')
109+ secret = base64.b64decode(secret[0])
110+ secret = self.crypto.CryptUnprotectData(secret,
111+ None, None, None, 0)
112+ admin_username, admin_password = secret[1].split(':', 1)
113+ else:
114+ admin_username = self.make_random_string(10)
115+ admin_password = self.make_random_string(10)
116+ # encrypt the info before we store it
117+ secret = ':'.join([admin_username, admin_password])
118+ secret = self.crypto.CryptProtectData(
119+ secret, u'basic', None, None, None, 0)
120+ secret = base64.b64encode(secret)
121+ # store the secured data
122+ self.registry.SetValueEx(key, 'basic', 0, self.registry.REG_SZ,
123+ secret)
124+ return (admin_username, admin_password)
125+
126+ def get_oauth_data(self):
127+ """Return the oauth data used to connect with couchdb."""
128+ consumer_key = self.make_random_string(10)
129+ consumer_secret = self.make_random_string(10)
130+ token = self.make_random_string(10)
131+ token_secret = self.make_random_string(10)
132+ # Save the new OAuth creds so that 3rd-party apps can
133+ # authenticate by accessing the keyring first. This is
134+ # one-way. We don't read from keyring.
135+ key = self._open_registry_key()
136+ secret = ':'.join([consumer_key, consumer_secret, token, token_secret])
137+ secret = self.crypto.CryptProtectData(secret, u'oauth',
138+ None, None, None, 0)
139+ secret = base64.b64encode(secret)
140+ self.registry.SetValueEx(key, 'oauth', 0, self.registry.REG_SZ,
141+ secret)
142+ return (consumer_key, consumer_secret, token, token_secret)
143
144=== added directory 'desktopcouch/application/platform/windows/tests'
145=== added file 'desktopcouch/application/platform/windows/tests/__init__.py'
146--- desktopcouch/application/platform/windows/tests/__init__.py 1970-01-01 00:00:00 +0000
147+++ desktopcouch/application/platform/windows/tests/__init__.py 2010-11-26 14:26:35 +0000
148@@ -0,0 +1,1 @@
149+"""Test windows platform code."""
150
151=== added file 'desktopcouch/application/platform/windows/tests/test_keyring.py'
152--- desktopcouch/application/platform/windows/tests/test_keyring.py 1970-01-01 00:00:00 +0000
153+++ desktopcouch/application/platform/windows/tests/test_keyring.py 2010-11-26 14:26:35 +0000
154@@ -0,0 +1,227 @@
155+# Copyright 2009 Canonical Ltd.
156+#
157+# This file is part of desktopcouch.
158+#
159+# desktopcouch is free software: you can redistribute it and/or modify
160+# it under the terms of the GNU Lesser General Public License version 3
161+# as published by the Free Software Foundation.
162+#
163+# desktopcouch is distributed in the hope that it will be useful,
164+# but WITHOUT ANY WARRANTY; without even the implied warranty of
165+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
166+# GNU Lesser General Public License for more details.
167+#
168+# You should have received a copy of the GNU Lesser General Public License
169+# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.
170+#
171+# Author: Manuel de la Pena <manuel.delapena@canonical.com>
172+"""Test keyring module."""
173+import base64
174+from mocker import MockerTestCase
175+
176+from desktopcouch.application.platform.windows.keyring import Keyring
177+
178+
179+class TestKeyring(MockerTestCase):
180+ """Test the class that manages the keyring on gnome."""
181+
182+ def setUp(self):
183+ super(TestKeyring, self).setUp()
184+ self._random_string_fn = self.mocker.mock()
185+ self._registry = self.mocker.mock()
186+ self._crypto = self.mocker.mock()
187+ self._keyring = Keyring(make_random_string_fn=self._random_string_fn,
188+ registry=self._registry, crypto=self._crypto)
189+ self._username = 'username'
190+ self._password = 'password'
191+ self._consumer_key = 'consumer_key'
192+ self._consumer_secret = 'consumer_secret'
193+ self._token = 'token'
194+ self._token_secret = 'token_secret'
195+ self._current_user = 'HKCU'
196+
197+ def test_get_user_name_password_no_key(self):
198+ """Test to get the value when the key is not present."""
199+ # try to open the key and thorw an exception
200+ # pylint: disable=W0104
201+ self._registry.HKEY_CURRENT_USER
202+ # pylint: enable=W0104
203+ self.mocker.result(0)
204+ # pylint: disable=W0104
205+ self._registry.KEY_ALL_ACCESS
206+ # pylint: enable=W0104
207+ self.mocker.result(0)
208+ self._registry.OpenKey(0, 'Canonical', 0, 0)
209+ self.mocker.throw(Exception('No key'))
210+ self._random_string_fn(10)
211+ self.mocker.result(self._username)
212+ self._random_string_fn(10)
213+ self.mocker.result(self._password)
214+ # open the key
215+ # pylint: disable=W0104
216+ self._registry.HKEY_CURRENT_USER
217+ # pylint: enable=W0104
218+ self.mocker.result(self._current_user)
219+ self._registry.CreateKey(self._current_user,
220+ 'Canonical')
221+ self.mocker.result(self._registry)
222+ self._registry.CreateKey(self._registry, 'Keyrings')
223+ self.mocker.result(self._registry)
224+ self._registry.CreateKey(self._registry, 'Default')
225+ self.mocker.result(self._registry)
226+ self._registry.CreateKey(self._registry, 'Desktopcouch')
227+ self.mocker.result(self._registry)
228+ # store the key
229+ self._crypto.CryptProtectData(
230+ ':'.join([self._username, self._password]),
231+ u'basic', None, None, None, 0)
232+ self.mocker.result('secret')
233+ # pylint: disable=W0104
234+ self._registry.REG_SZ
235+ # pylint: enable=W0104
236+ self.mocker.result(0)
237+ self._registry.SetValueEx(self._registry, 'basic', 0,
238+ 0, base64.b64encode('secret'))
239+ self.mocker.replay()
240+ username, password = self._keyring.get_user_name_password()
241+ self.assertEqual(username, self._username)
242+ self.assertEqual(password, self._password)
243+
244+ def test_get_user_name_password_no_value(self):
245+ """Test to get the value when it is no present in the key."""
246+ # pylint: disable=W0104
247+ self._registry.HKEY_CURRENT_USER
248+ self.mocker.result(0)
249+ self._registry.KEY_ALL_ACCESS
250+ self.mocker.result(0)
251+ self._registry.OpenKey(0, 'Canonical', 0, 0)
252+ self.mocker.result(self._registry)
253+ self._registry.OpenKey(self._registry, 'Keyrings', 0, 0)
254+ self.mocker.result(self._registry)
255+ self._registry.OpenKey(self._registry, 'Default', 0, 0)
256+ self.mocker.result(self._registry)
257+ self._registry.OpenKey(self._registry, 'Desktopcouch', 0, 0)
258+ self.mocker.result(self._registry)
259+ # pylint: enable=W0104
260+ self._registry.EnumValue(self._registry, 0)
261+ self.mocker.throw(Exception('No values'))
262+ self._random_string_fn(10)
263+ self.mocker.result(self._username)
264+ self._random_string_fn(10)
265+ self.mocker.result(self._password)
266+ # open the key
267+ # pylint: disable=W0104
268+ self._registry.HKEY_CURRENT_USER
269+ # pylint: enable=W0104
270+ self.mocker.result(self._current_user)
271+ self._registry.CreateKey(self._current_user,
272+ 'Canonical')
273+ self.mocker.result(self._registry)
274+ self._registry.CreateKey(self._registry, 'Keyrings')
275+ self.mocker.result(self._registry)
276+ self._registry.CreateKey(self._registry, 'Default')
277+ self.mocker.result(self._registry)
278+ self._registry.CreateKey(self._registry, 'Desktopcouch')
279+ self.mocker.result(self._registry)
280+ # store the key
281+ self._crypto.CryptProtectData(
282+ ':'.join([self._username, self._password]),
283+ u'basic', None, None, None, 0)
284+ self.mocker.result('secret')
285+ # pylint: disable=W0104
286+ self._registry.REG_SZ
287+ # pylint: enable=W0104
288+ self.mocker.result(0)
289+ self._registry.SetValueEx(self._registry, 'basic', 0,
290+ 0, base64.b64encode('secret'))
291+ self.mocker.replay()
292+ username, password = self._keyring.get_user_name_password()
293+ self.assertEqual(username, self._username)
294+ self.assertEqual(password, self._password)
295+
296+ def test_get_user_name_password(self):
297+ """Test get the data when it is present."""
298+ # pylint: disable=W0104
299+ self._registry.HKEY_CURRENT_USER
300+ self.mocker.result(0)
301+ self._registry.KEY_ALL_ACCESS
302+ self.mocker.result(0)
303+ self._registry.OpenKey(0, 'Canonical', 0, 0)
304+ self.mocker.result(self._registry)
305+ self._registry.OpenKey(self._registry, 'Keyrings', 0, 0)
306+ self.mocker.result(self._registry)
307+ self._registry.OpenKey(self._registry, 'Default', 0, 0)
308+ self.mocker.result(self._registry)
309+ self._registry.OpenKey(self._registry, 'Desktopcouch', 0, 0)
310+ self.mocker.result(self._registry)
311+ # pylint: enable=W0104
312+ self._registry.EnumValue(self._registry, 0)
313+ self.mocker.result(['basic'])
314+ # pylint: disable=W0104
315+ self._registry.HKEY_CURRENT_USER
316+ # pylint: enable=W0104
317+ self.mocker.result(self._current_user)
318+ self._registry.CreateKey(self._current_user,
319+ 'Canonical')
320+ self.mocker.result(self._registry)
321+ self._registry.CreateKey(self._registry, 'Keyrings')
322+ self.mocker.result(self._registry)
323+ self._registry.CreateKey(self._registry, 'Default')
324+ self.mocker.result(self._registry)
325+ self._registry.CreateKey(self._registry, 'Desktopcouch')
326+ self.mocker.result(self._registry)
327+ # get the data from the key
328+ self._registry.QueryValueEx(self._registry, 'basic')
329+ self.mocker.result([base64.b64encode('secret')])
330+ self._crypto.CryptUnprotectData('secret', None, None, None, 0)
331+ self.mocker.result([0, ':'.join([self._username, self._password])])
332+ self.mocker.replay()
333+ username, password = self._keyring.get_user_name_password()
334+ self.assertEqual(username, self._username)
335+ self.assertEqual(password, self._password)
336+
337+ def test_get_oauth_data(self):
338+ """Test get the oauth data."""
339+ secret_data = ':'.join([self._consumer_key,
340+ self._consumer_secret,
341+ self._token,
342+ self._token_secret])
343+ self._random_string_fn(10)
344+ self.mocker.result(self._consumer_key)
345+ self._random_string_fn(10)
346+ self.mocker.result(self._consumer_secret)
347+ self._random_string_fn(10)
348+ self.mocker.result(self._token)
349+ self._random_string_fn(10)
350+ self.mocker.result(self._token_secret)
351+ # open the key
352+ # pylint: disable=W0104
353+ self._registry.HKEY_CURRENT_USER
354+ # pylint: enable=W0104
355+ self.mocker.result(self._current_user)
356+ self._registry.CreateKey(self._current_user,
357+ 'Canonical')
358+ self.mocker.result(self._registry)
359+ self._registry.CreateKey(self._registry, 'Keyrings')
360+ self.mocker.result(self._registry)
361+ self._registry.CreateKey(self._registry, 'Default')
362+ self.mocker.result(self._registry)
363+ self._registry.CreateKey(self._registry, 'Desktopcouch')
364+ self.mocker.result(self._registry)
365+ # store the key
366+ self._crypto.CryptProtectData(secret_data,
367+ 'oauth', None, None, None, 0)
368+ self.mocker.result('secret')
369+ # pylint: disable=W0104
370+ self._registry.REG_SZ
371+ # pylint: enable=W0104
372+ self.mocker.result(0)
373+ self._registry.SetValueEx(self._registry, 'oauth', 0, 0,
374+ base64.b64encode('secret'))
375+ self.mocker.replay()
376+ consumer_key, consumer_secret, token, token_secret = \
377+ self._keyring.get_oauth_data()
378+ self.assertEqual(self._consumer_key, consumer_key)
379+ self.assertEqual(self._consumer_secret, consumer_secret)
380+ self.assertEqual(self._token, token)
381+ self.assertEqual(self._token_secret, token_secret)

Subscribers

People subscribed via source and target branches