test_credential_store.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. # Copyright 2010-2011 Canonical Ltd.
  2. # This file is part of launchpadlib.
  3. #
  4. # launchpadlib is free software: you can redistribute it and/or modify it
  5. # under the terms of the GNU Lesser General Public License as published by the
  6. # Free Software Foundation, version 3 of the License.
  7. #
  8. # launchpadlib is distributed in the hope that it will be useful, but WITHOUT
  9. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
  11. # for more details.
  12. #
  13. # You should have received a copy of the GNU Lesser General Public License
  14. # along with launchpadlib. If not, see <http://www.gnu.org/licenses/>.
  15. """Tests for the credential store classes."""
  16. import os
  17. import tempfile
  18. import unittest
  19. from base64 import b64decode
  20. if bytes is str:
  21. # Python 2
  22. unicode_type = unicode # noqa: F821
  23. else:
  24. unicode_type = str
  25. from launchpadlib.testing.helpers import (
  26. fake_keyring,
  27. InMemoryKeyring,
  28. )
  29. from launchpadlib.credentials import (
  30. AccessToken,
  31. Credentials,
  32. KeyringCredentialStore,
  33. UnencryptedFileCredentialStore,
  34. )
  35. class TestAccessToken(unittest.TestCase):
  36. """Tests for the AccessToken class."""
  37. def test_from_string(self):
  38. access_token = AccessToken.from_string(
  39. "oauth_token_secret=secret%3Dpassword&oauth_token=lock%26key"
  40. )
  41. self.assertEqual("lock&key", access_token.key)
  42. self.assertEqual("secret=password", access_token.secret)
  43. self.assertIsNone(access_token.context)
  44. def test_from_string_with_context(self):
  45. access_token = AccessToken.from_string(
  46. "oauth_token_secret=secret%3Dpassword&oauth_token=lock%26key&"
  47. "lp.context=firefox"
  48. )
  49. self.assertEqual("lock&key", access_token.key)
  50. self.assertEqual("secret=password", access_token.secret)
  51. self.assertEqual("firefox", access_token.context)
  52. class CredentialStoreTestCase(unittest.TestCase):
  53. def make_credential(self, consumer_key):
  54. """Helper method to make a fake credential."""
  55. return Credentials(
  56. "app name",
  57. consumer_secret="consumer_secret:42",
  58. access_token=AccessToken(consumer_key, "access_secret:168"),
  59. )
  60. class TestUnencryptedFileCredentialStore(CredentialStoreTestCase):
  61. """Tests for the UnencryptedFileCredentialStore class."""
  62. def setUp(self):
  63. ignore, self.filename = tempfile.mkstemp()
  64. self.store = UnencryptedFileCredentialStore(self.filename)
  65. def tearDown(self):
  66. if os.path.exists(self.filename):
  67. os.remove(self.filename)
  68. def test_save_and_load(self):
  69. # Make sure you can save and load credentials to a file.
  70. credential = self.make_credential("consumer key")
  71. self.store.save(credential, "unique key")
  72. credential2 = self.store.load("unique key")
  73. self.assertEqual(credential.consumer.key, credential2.consumer.key)
  74. def test_unique_id_doesnt_matter(self):
  75. # If a file contains a credential, that credential will be
  76. # accessed no matter what unique ID you specify.
  77. credential = self.make_credential("consumer key")
  78. self.store.save(credential, "some key")
  79. credential2 = self.store.load("some other key")
  80. self.assertEqual(credential.consumer.key, credential2.consumer.key)
  81. def test_file_only_contains_one_credential(self):
  82. # A credential file may contain only one credential. If you
  83. # write two credentials with different unique IDs to the same
  84. # file, the first credential will be overwritten with the
  85. # second.
  86. credential1 = self.make_credential("consumer key")
  87. credential2 = self.make_credential("consumer key2")
  88. self.store.save(credential1, "unique key 1")
  89. self.store.save(credential1, "unique key 2")
  90. loaded = self.store.load("unique key 1")
  91. self.assertEqual(loaded.consumer.key, credential2.consumer.key)
  92. class TestKeyringCredentialStore(CredentialStoreTestCase):
  93. """Tests for the KeyringCredentialStore class."""
  94. def setUp(self):
  95. self.keyring = InMemoryKeyring()
  96. self.store = KeyringCredentialStore()
  97. def test_save_and_load(self):
  98. # Make sure you can save and load credentials to a keyring.
  99. with fake_keyring(self.keyring):
  100. credential = self.make_credential("consumer key")
  101. self.store.save(credential, "unique key")
  102. credential2 = self.store.load("unique key")
  103. self.assertEqual(credential.consumer.key, credential2.consumer.key)
  104. def test_lookup_by_unique_key(self):
  105. # Credentials in the keyring are looked up by the unique ID
  106. # under which they were stored.
  107. with fake_keyring(self.keyring):
  108. credential1 = self.make_credential("consumer key1")
  109. self.store.save(credential1, "key 1")
  110. credential2 = self.make_credential("consumer key2")
  111. self.store.save(credential2, "key 2")
  112. loaded1 = self.store.load("key 1")
  113. self.assertTrue(loaded1)
  114. self.assertEqual(credential1.consumer.key, loaded1.consumer.key)
  115. loaded2 = self.store.load("key 2")
  116. self.assertEqual(credential2.consumer.key, loaded2.consumer.key)
  117. def test_reused_unique_id_overwrites_old_credential(self):
  118. # Writing a credential to the keyring with a given unique ID
  119. # will overwrite any credential stored under that ID.
  120. with fake_keyring(self.keyring):
  121. credential1 = self.make_credential("consumer key1")
  122. self.store.save(credential1, "the only key")
  123. credential2 = self.make_credential("consumer key2")
  124. self.store.save(credential2, "the only key")
  125. loaded = self.store.load("the only key")
  126. self.assertEqual(credential2.consumer.key, loaded.consumer.key)
  127. def test_bad_unique_id_returns_none(self):
  128. # Trying to load a credential without providing a good unique
  129. # ID will get you None.
  130. with fake_keyring(self.keyring):
  131. self.assertIsNone(self.store.load("no such key"))
  132. def test_keyring_returns_unicode(self):
  133. # Kwallet is reported to sometimes return Unicode, which broke the
  134. # credentials parsing. This test ensures a Unicode password is
  135. # handled correctly. (See bug lp:877374)
  136. class UnicodeInMemoryKeyring(InMemoryKeyring):
  137. def get_password(self, service, username):
  138. password = super(UnicodeInMemoryKeyring, self).get_password(
  139. service, username
  140. )
  141. if isinstance(password, unicode_type):
  142. password = password.encode("utf-8")
  143. return password
  144. self.keyring = UnicodeInMemoryKeyring()
  145. with fake_keyring(self.keyring):
  146. credential = self.make_credential("consumer key")
  147. self.assertTrue(credential)
  148. # Shouldn't this test actually use a unicodish key?!
  149. self.store.save(credential, "unique key")
  150. credential2 = self.store.load("unique key")
  151. self.assertTrue(credential2)
  152. self.assertEqual(credential.consumer.key, credential2.consumer.key)
  153. self.assertEqual(
  154. credential.consumer.secret, credential2.consumer.secret
  155. )
  156. def test_nonencoded_key_handled(self):
  157. # For backwards compatibility with keys that are not base 64 encoded.
  158. class UnencodedInMemoryKeyring(InMemoryKeyring):
  159. def get_password(self, service, username):
  160. pw = super(UnencodedInMemoryKeyring, self).get_password(
  161. service, username
  162. )
  163. return b64decode(pw[5:])
  164. self.keyring = UnencodedInMemoryKeyring()
  165. with fake_keyring(self.keyring):
  166. credential = self.make_credential("consumer key")
  167. self.store.save(credential, "unique key")
  168. credential2 = self.store.load("unique key")
  169. self.assertEqual(credential.consumer.key, credential2.consumer.key)
  170. self.assertEqual(
  171. credential.consumer.secret, credential2.consumer.secret
  172. )
  173. def test_corrupted_key_handled(self):
  174. # A corrupted password results in None being returned.
  175. class CorruptedInMemoryKeyring(InMemoryKeyring):
  176. def get_password(self, service, username):
  177. return "bad"
  178. self.keyring = CorruptedInMemoryKeyring()
  179. with fake_keyring(self.keyring):
  180. credential = self.make_credential("consumer key")
  181. self.store.save(credential, "unique key")
  182. credential2 = self.store.load("unique key")
  183. self.assertIsNone(credential2)