/openid/test/test_negotiation.py

https://github.com/ziima/python-openid · Python · 285 lines · 231 code · 17 blank · 37 comment · 5 complexity · 34f4dfe7ba3174b54d9f548e3ce27683 MD5 · raw file

  1. from __future__ import unicode_literals
  2. import unittest
  3. from testfixtures import LogCapture, StringComparison
  4. from openid import association
  5. from openid.consumer.consumer import GenericConsumer, ServerError
  6. from openid.consumer.discover import OPENID_2_0_TYPE, OpenIDServiceEndpoint
  7. from openid.message import OPENID1_NS, OPENID_NS, Message
  8. class ErrorRaisingConsumer(GenericConsumer):
  9. """
  10. A consumer whose _requestAssocation will return predefined results
  11. instead of trying to actually perform association requests.
  12. """
  13. # The list of objects to be returned by successive calls to
  14. # _requestAssocation. Each call will pop the first element from
  15. # this list and return it to _negotiateAssociation. If the
  16. # element is a Message object, it will be wrapped in a ServerError
  17. # exception. Otherwise it will be returned as-is.
  18. return_messages = []
  19. def _requestAssociation(self, endpoint, assoc_type, session_type):
  20. m = self.return_messages.pop(0)
  21. if isinstance(m, Message):
  22. raise ServerError.fromMessage(m)
  23. else:
  24. return m
  25. class TestOpenID2SessionNegotiation(unittest.TestCase):
  26. """
  27. Test the session type negotiation behavior of an OpenID 2
  28. consumer.
  29. """
  30. def setUp(self):
  31. self.consumer = ErrorRaisingConsumer(store=None)
  32. self.endpoint = OpenIDServiceEndpoint()
  33. self.endpoint.type_uris = [OPENID_2_0_TYPE]
  34. self.endpoint.server_url = 'bogus'
  35. def testBadResponse(self):
  36. """
  37. Test the case where the response to an associate request is a
  38. server error or is otherwise undecipherable.
  39. """
  40. self.consumer.return_messages = [Message(self.endpoint.preferredNamespace())]
  41. with LogCapture() as logbook:
  42. self.assertEqual(self.consumer._negotiateAssociation(self.endpoint), None)
  43. logbook.check(
  44. ('openid.consumer.consumer', 'ERROR', StringComparison('Server error when requesting an association .*')))
  45. def testEmptyAssocType(self):
  46. """
  47. Test the case where the association type (assoc_type) returned
  48. in an unsupported-type response is absent.
  49. """
  50. msg = Message(self.endpoint.preferredNamespace())
  51. msg.setArg(OPENID_NS, 'error', 'Unsupported type')
  52. msg.setArg(OPENID_NS, 'error_code', 'unsupported-type')
  53. # not set: msg.delArg(OPENID_NS, 'assoc_type')
  54. msg.setArg(OPENID_NS, 'session_type', 'new-session-type')
  55. self.consumer.return_messages = [msg]
  56. with LogCapture() as logbook:
  57. self.assertIsNone(self.consumer._negotiateAssociation(self.endpoint))
  58. no_fallback_msg = 'Server responded with unsupported association session but did not supply a fallback.'
  59. logbook.check(('openid.consumer.consumer', 'WARNING', StringComparison('Unsupported association type .*')),
  60. ('openid.consumer.consumer', 'WARNING', no_fallback_msg))
  61. def testEmptySessionType(self):
  62. """
  63. Test the case where the session type (session_type) returned
  64. in an unsupported-type response is absent.
  65. """
  66. msg = Message(self.endpoint.preferredNamespace())
  67. msg.setArg(OPENID_NS, 'error', 'Unsupported type')
  68. msg.setArg(OPENID_NS, 'error_code', 'unsupported-type')
  69. msg.setArg(OPENID_NS, 'assoc_type', 'new-assoc-type')
  70. # not set: msg.setArg(OPENID_NS, 'session_type', None)
  71. self.consumer.return_messages = [msg]
  72. with LogCapture() as logbook:
  73. self.assertIsNone(self.consumer._negotiateAssociation(self.endpoint))
  74. no_fallback_msg = 'Server responded with unsupported association session but did not supply a fallback.'
  75. logbook.check(('openid.consumer.consumer', 'WARNING', StringComparison('Unsupported association type .*')),
  76. ('openid.consumer.consumer', 'WARNING', no_fallback_msg))
  77. def testNotAllowed(self):
  78. """
  79. Test the case where an unsupported-type response specifies a
  80. preferred (assoc_type, session_type) combination that is not
  81. allowed by the consumer's SessionNegotiator.
  82. """
  83. allowed_types = []
  84. negotiator = association.SessionNegotiator(allowed_types)
  85. self.consumer.negotiator = negotiator
  86. msg = Message(self.endpoint.preferredNamespace())
  87. msg.setArg(OPENID_NS, 'error', 'Unsupported type')
  88. msg.setArg(OPENID_NS, 'error_code', 'unsupported-type')
  89. msg.setArg(OPENID_NS, 'assoc_type', 'not-allowed')
  90. msg.setArg(OPENID_NS, 'session_type', 'not-allowed')
  91. self.consumer.return_messages = [msg]
  92. with LogCapture() as logbook:
  93. self.assertIsNone(self.consumer._negotiateAssociation(self.endpoint))
  94. unsupported_msg = StringComparison('Server sent unsupported session/association type: .*')
  95. logbook.check(('openid.consumer.consumer', 'WARNING', StringComparison('Unsupported association type .*')),
  96. ('openid.consumer.consumer', 'WARNING', unsupported_msg))
  97. def testUnsupportedWithRetry(self):
  98. """
  99. Test the case where an unsupported-type response triggers a
  100. retry to get an association with the new preferred type.
  101. """
  102. msg = Message(self.endpoint.preferredNamespace())
  103. msg.setArg(OPENID_NS, 'error', 'Unsupported type')
  104. msg.setArg(OPENID_NS, 'error_code', 'unsupported-type')
  105. msg.setArg(OPENID_NS, 'assoc_type', 'HMAC-SHA1')
  106. msg.setArg(OPENID_NS, 'session_type', 'DH-SHA1')
  107. assoc = association.Association('handle', b'secret', 'issued', 10000, 'HMAC-SHA1')
  108. self.consumer.return_messages = [msg, assoc]
  109. with LogCapture() as logbook:
  110. self.assertEqual(self.consumer._negotiateAssociation(self.endpoint), assoc)
  111. logbook.check(('openid.consumer.consumer', 'WARNING', StringComparison('Unsupported association type .*')))
  112. def testUnsupportedWithRetryAndFail(self):
  113. """
  114. Test the case where an unsupported-typ response triggers a
  115. retry, but the retry fails and None is returned instead.
  116. """
  117. msg = Message(self.endpoint.preferredNamespace())
  118. msg.setArg(OPENID_NS, 'error', 'Unsupported type')
  119. msg.setArg(OPENID_NS, 'error_code', 'unsupported-type')
  120. msg.setArg(OPENID_NS, 'assoc_type', 'HMAC-SHA1')
  121. msg.setArg(OPENID_NS, 'session_type', 'DH-SHA1')
  122. self.consumer.return_messages = [msg,
  123. Message(self.endpoint.preferredNamespace())]
  124. with LogCapture() as logbook:
  125. self.assertIsNone(self.consumer._negotiateAssociation(self.endpoint))
  126. refused_msg = StringComparison('Server %s refused its .*' % self.endpoint.server_url)
  127. logbook.check(('openid.consumer.consumer', 'WARNING', StringComparison('Unsupported association type .*')),
  128. ('openid.consumer.consumer', 'ERROR', refused_msg))
  129. def testValid(self):
  130. """
  131. Test the valid case, wherein an association is returned on the
  132. first attempt to get one.
  133. """
  134. assoc = association.Association('handle', b'secret', 'issued', 10000, 'HMAC-SHA1')
  135. self.consumer.return_messages = [assoc]
  136. with LogCapture() as logbook:
  137. self.assertEqual(self.consumer._negotiateAssociation(self.endpoint), assoc)
  138. self.assertEqual(logbook.records, [])
  139. class TestOpenID1SessionNegotiation(unittest.TestCase):
  140. """
  141. Tests for the OpenID 1 consumer association session behavior. See
  142. the docs for TestOpenID2SessionNegotiation. Notice that this
  143. class is not a subclass of the OpenID 2 tests. Instead, it uses
  144. many of the same inputs but inspects the log messages, see the LogCapture.
  145. Some of these tests pass openid2-style messages to the openid 1
  146. association processing logic to be sure it ignores the extra data.
  147. """
  148. def setUp(self):
  149. self.consumer = ErrorRaisingConsumer(store=None)
  150. self.endpoint = OpenIDServiceEndpoint()
  151. self.endpoint.type_uris = [OPENID1_NS]
  152. self.endpoint.server_url = 'bogus'
  153. def testBadResponse(self):
  154. self.consumer.return_messages = [Message(self.endpoint.preferredNamespace())]
  155. with LogCapture() as logbook:
  156. self.assertIsNone(self.consumer._negotiateAssociation(self.endpoint))
  157. logbook.check(
  158. ('openid.consumer.consumer', 'ERROR', StringComparison('Server error when requesting an association .*')))
  159. def testEmptyAssocType(self):
  160. msg = Message(self.endpoint.preferredNamespace())
  161. msg.setArg(OPENID_NS, 'error', 'Unsupported type')
  162. msg.setArg(OPENID_NS, 'error_code', 'unsupported-type')
  163. # not set: msg.setArg(OPENID_NS, 'assoc_type', None)
  164. msg.setArg(OPENID_NS, 'session_type', 'new-session-type')
  165. self.consumer.return_messages = [msg]
  166. with LogCapture() as logbook:
  167. self.assertIsNone(self.consumer._negotiateAssociation(self.endpoint))
  168. logbook.check(
  169. ('openid.consumer.consumer', 'ERROR', StringComparison('Server error when requesting an association .*')))
  170. def testEmptySessionType(self):
  171. msg = Message(self.endpoint.preferredNamespace())
  172. msg.setArg(OPENID_NS, 'error', 'Unsupported type')
  173. msg.setArg(OPENID_NS, 'error_code', 'unsupported-type')
  174. msg.setArg(OPENID_NS, 'assoc_type', 'new-assoc-type')
  175. # not set: msg.setArg(OPENID_NS, 'session_type', None)
  176. self.consumer.return_messages = [msg]
  177. with LogCapture() as logbook:
  178. self.assertIsNone(self.consumer._negotiateAssociation(self.endpoint))
  179. logbook.check(
  180. ('openid.consumer.consumer', 'ERROR', StringComparison('Server error when requesting an association .*')))
  181. def testNotAllowed(self):
  182. allowed_types = []
  183. negotiator = association.SessionNegotiator(allowed_types)
  184. self.consumer.negotiator = negotiator
  185. msg = Message(self.endpoint.preferredNamespace())
  186. msg.setArg(OPENID_NS, 'error', 'Unsupported type')
  187. msg.setArg(OPENID_NS, 'error_code', 'unsupported-type')
  188. msg.setArg(OPENID_NS, 'assoc_type', 'not-allowed')
  189. msg.setArg(OPENID_NS, 'session_type', 'not-allowed')
  190. self.consumer.return_messages = [msg]
  191. with LogCapture() as logbook:
  192. self.assertIsNone(self.consumer._negotiateAssociation(self.endpoint))
  193. logbook.check(
  194. ('openid.consumer.consumer', 'ERROR', StringComparison('Server error when requesting an association .*')))
  195. def testUnsupportedWithRetry(self):
  196. msg = Message(self.endpoint.preferredNamespace())
  197. msg.setArg(OPENID_NS, 'error', 'Unsupported type')
  198. msg.setArg(OPENID_NS, 'error_code', 'unsupported-type')
  199. msg.setArg(OPENID_NS, 'assoc_type', 'HMAC-SHA1')
  200. msg.setArg(OPENID_NS, 'session_type', 'DH-SHA1')
  201. assoc = association.Association('handle', b'secret', 'issued', 10000, 'HMAC-SHA1')
  202. self.consumer.return_messages = [msg, assoc]
  203. with LogCapture() as logbook:
  204. self.assertIsNone(self.consumer._negotiateAssociation(self.endpoint))
  205. logbook.check(
  206. ('openid.consumer.consumer', 'ERROR', StringComparison('Server error when requesting an association .*')))
  207. def testValid(self):
  208. assoc = association.Association('handle', b'secret', 'issued', 10000, 'HMAC-SHA1')
  209. self.consumer.return_messages = [assoc]
  210. with LogCapture() as logbook:
  211. self.assertEqual(self.consumer._negotiateAssociation(self.endpoint), assoc)
  212. self.assertEqual(logbook.records, [])
  213. class TestNegotiatorBehaviors(unittest.TestCase):
  214. def setUp(self):
  215. self.allowed_types = [
  216. ('HMAC-SHA1', 'no-encryption'),
  217. ('HMAC-SHA256', 'no-encryption'),
  218. ]
  219. self.n = association.SessionNegotiator(self.allowed_types)
  220. def testAddAllowedTypeNoSessionTypes(self):
  221. self.assertRaises(ValueError, self.n.addAllowedType, 'invalid')
  222. def testAddAllowedTypeBadSessionType(self):
  223. self.assertRaises(ValueError, self.n.addAllowedType, 'assoc1', 'invalid')
  224. def testAddAllowedTypeContents(self):
  225. assoc_type = 'HMAC-SHA1'
  226. self.assertIsNone(self.n.addAllowedType(assoc_type))
  227. for typ in association.getSessionTypes(assoc_type):
  228. self.assertIn((assoc_type, typ), self.n.allowed_types)
  229. if __name__ == '__main__':
  230. unittest.main()