PageRenderTime 34ms CodeModel.GetById 39ms RepoModel.GetById 0ms app.codeStats 1ms

/vendor/gems/ruby-openid-2.1.2/test/test_associationmanager.rb

https://github.com/hariis/bort
Ruby | 917 lines | 689 code | 143 blank | 85 comment | 6 complexity | f7c350ec891d7270b81c8018379b2830 MD5 | raw file
Possible License(s): Apache-2.0
  1. require "openid/consumer/associationmanager"
  2. require "openid/association"
  3. require "openid/dh"
  4. require "openid/util"
  5. require "openid/cryptutil"
  6. require "openid/message"
  7. require "openid/store/memory"
  8. require "test/unit"
  9. require "util"
  10. require "time"
  11. module OpenID
  12. class DHAssocSessionTest < Test::Unit::TestCase
  13. def test_sha1_get_request
  14. # Initialized without an explicit DH gets defaults
  15. sess = Consumer::DiffieHellmanSHA1Session.new
  16. assert_equal(['dh_consumer_public'], sess.get_request.keys)
  17. assert_nothing_raised do
  18. Util::from_base64(sess.get_request['dh_consumer_public'])
  19. end
  20. end
  21. def test_sha1_get_request_custom_dh
  22. dh = DiffieHellman.new(1299721, 2)
  23. sess = Consumer::DiffieHellmanSHA1Session.new(dh)
  24. req = sess.get_request
  25. assert_equal(['dh_consumer_public', 'dh_modulus', 'dh_gen'].sort,
  26. req.keys.sort)
  27. assert_equal(dh.modulus, CryptUtil.base64_to_num(req['dh_modulus']))
  28. assert_equal(dh.generator, CryptUtil.base64_to_num(req['dh_gen']))
  29. assert_nothing_raised do
  30. Util::from_base64(req['dh_consumer_public'])
  31. end
  32. end
  33. end
  34. module TestDiffieHellmanResponseParametersMixin
  35. def setup
  36. session_cls = self.class.session_cls
  37. # Pre-compute DH with small prime so tests run quickly.
  38. @server_dh = DiffieHellman.new(100389557, 2)
  39. @consumer_dh = DiffieHellman.new(100389557, 2)
  40. # base64(btwoc(g ^ xb mod p))
  41. @dh_server_public = CryptUtil.num_to_base64(@server_dh.public)
  42. @secret = CryptUtil.random_string(session_cls.secret_size)
  43. enc_mac_key_unencoded =
  44. @server_dh.xor_secret(session_cls.hashfunc,
  45. @consumer_dh.public,
  46. @secret)
  47. @enc_mac_key = Util.to_base64(enc_mac_key_unencoded)
  48. @consumer_session = session_cls.new(@consumer_dh)
  49. @msg = Message.new(self.class.message_namespace)
  50. end
  51. def test_extract_secret
  52. @msg.set_arg(OPENID_NS, 'dh_server_public', @dh_server_public)
  53. @msg.set_arg(OPENID_NS, 'enc_mac_key', @enc_mac_key)
  54. extracted = @consumer_session.extract_secret(@msg)
  55. assert_equal(extracted, @secret)
  56. end
  57. def test_absent_serve_public
  58. @msg.set_arg(OPENID_NS, 'enc_mac_key', @enc_mac_key)
  59. assert_raises(Message::KeyNotFound) {
  60. @consumer_session.extract_secret(@msg)
  61. }
  62. end
  63. def test_absent_mac_key
  64. @msg.set_arg(OPENID_NS, 'dh_server_public', @dh_server_public)
  65. assert_raises(Message::KeyNotFound) {
  66. @consumer_session.extract_secret(@msg)
  67. }
  68. end
  69. def test_invalid_base64_public
  70. @msg.set_arg(OPENID_NS, 'dh_server_public', 'n o t b a s e 6 4.')
  71. @msg.set_arg(OPENID_NS, 'enc_mac_key', @enc_mac_key)
  72. assert_raises(ArgumentError) {
  73. @consumer_session.extract_secret(@msg)
  74. }
  75. end
  76. def test_invalid_base64_mac_key
  77. @msg.set_arg(OPENID_NS, 'dh_server_public', @dh_server_public)
  78. @msg.set_arg(OPENID_NS, 'enc_mac_key', 'n o t base 64')
  79. assert_raises(ArgumentError) {
  80. @consumer_session.extract_secret(@msg)
  81. }
  82. end
  83. end
  84. class TestConsumerOpenID1DHSHA1 < Test::Unit::TestCase
  85. include TestDiffieHellmanResponseParametersMixin
  86. class << self
  87. attr_reader :session_cls, :message_namespace
  88. end
  89. @session_cls = Consumer::DiffieHellmanSHA1Session
  90. @message_namespace = OPENID1_NS
  91. end
  92. class TestConsumerOpenID2DHSHA1 < Test::Unit::TestCase
  93. include TestDiffieHellmanResponseParametersMixin
  94. class << self
  95. attr_reader :session_cls, :message_namespace
  96. end
  97. @session_cls = Consumer::DiffieHellmanSHA1Session
  98. @message_namespace = OPENID2_NS
  99. end
  100. class TestConsumerOpenID2DHSHA256 < Test::Unit::TestCase
  101. include TestDiffieHellmanResponseParametersMixin
  102. class << self
  103. attr_reader :session_cls, :message_namespace
  104. end
  105. @session_cls = Consumer::DiffieHellmanSHA256Session
  106. @message_namespace = OPENID2_NS
  107. end
  108. class TestConsumerNoEncryptionSession < Test::Unit::TestCase
  109. def setup
  110. @sess = Consumer::NoEncryptionSession.new
  111. end
  112. def test_empty_request
  113. assert_equal(@sess.get_request, {})
  114. end
  115. def test_get_secret
  116. secret = 'shhh!' * 4
  117. mac_key = Util.to_base64(secret)
  118. msg = Message.from_openid_args({'mac_key' => mac_key})
  119. assert_equal(secret, @sess.extract_secret(msg))
  120. end
  121. end
  122. class TestCreateAssociationRequest < Test::Unit::TestCase
  123. def setup
  124. @server_url = 'http://invalid/'
  125. @assoc_manager = Consumer::AssociationManager.new(nil, @server_url)
  126. class << @assoc_manager
  127. def compatibility_mode=(val)
  128. @compatibility_mode = val
  129. end
  130. end
  131. @assoc_type = 'HMAC-SHA1'
  132. end
  133. def test_no_encryption_sends_type
  134. session_type = 'no-encryption'
  135. session, args = @assoc_manager.send(:create_associate_request,
  136. @assoc_type,
  137. session_type)
  138. assert(session.is_a?(Consumer::NoEncryptionSession))
  139. expected = Message.from_openid_args(
  140. {'ns' => OPENID2_NS,
  141. 'session_type' => session_type,
  142. 'mode' => 'associate',
  143. 'assoc_type' => @assoc_type,
  144. })
  145. assert_equal(expected, args)
  146. end
  147. def test_no_encryption_compatibility
  148. @assoc_manager.compatibility_mode = true
  149. session_type = 'no-encryption'
  150. session, args = @assoc_manager.send(:create_associate_request,
  151. @assoc_type,
  152. session_type)
  153. assert(session.is_a?(Consumer::NoEncryptionSession))
  154. assert_equal(Message.from_openid_args({'mode' => 'associate',
  155. 'assoc_type' => @assoc_type,
  156. }), args)
  157. end
  158. def test_dh_sha1_compatibility
  159. @assoc_manager.compatibility_mode = true
  160. session_type = 'DH-SHA1'
  161. session, args = @assoc_manager.send(:create_associate_request,
  162. @assoc_type,
  163. session_type)
  164. assert(session.is_a?(Consumer::DiffieHellmanSHA1Session))
  165. # This is a random base-64 value, so just check that it's
  166. # present.
  167. assert_not_nil(args.get_arg(OPENID1_NS, 'dh_consumer_public'))
  168. args.del_arg(OPENID1_NS, 'dh_consumer_public')
  169. # OK, session_type is set here and not for no-encryption
  170. # compatibility
  171. expected = Message.from_openid_args({'mode' => 'associate',
  172. 'session_type' => 'DH-SHA1',
  173. 'assoc_type' => @assoc_type,
  174. })
  175. assert_equal(expected, args)
  176. end
  177. end
  178. class TestAssociationManagerExpiresIn < Test::Unit::TestCase
  179. def expires_in_msg(val)
  180. msg = Message.from_openid_args({'expires_in' => val})
  181. Consumer::AssociationManager.extract_expires_in(msg)
  182. end
  183. def test_parse_fail
  184. ['',
  185. '-2',
  186. ' 1',
  187. ' ',
  188. '0x00',
  189. 'foosball',
  190. '1\n',
  191. '100,000,000,000',
  192. ].each do |x|
  193. assert_raises(ProtocolError) {expires_in_msg(x)}
  194. end
  195. end
  196. def test_parse
  197. ['0',
  198. '1',
  199. '1000',
  200. '9999999',
  201. '01',
  202. ].each do |n|
  203. assert_equal(n.to_i, expires_in_msg(n))
  204. end
  205. end
  206. end
  207. class TestAssociationManagerCreateSession < Test::Unit::TestCase
  208. def test_invalid
  209. assert_raises(ArgumentError) {
  210. Consumer::AssociationManager.create_session('monkeys')
  211. }
  212. end
  213. def test_sha256
  214. sess = Consumer::AssociationManager.create_session('DH-SHA256')
  215. assert(sess.is_a?(Consumer::DiffieHellmanSHA256Session))
  216. end
  217. end
  218. module NegotiationTestMixin
  219. include TestUtil
  220. def mk_message(args)
  221. args['ns'] = @openid_ns
  222. Message.from_openid_args(args)
  223. end
  224. def call_negotiate(responses, negotiator=nil)
  225. store = nil
  226. compat = self.class::Compat
  227. assoc_manager = Consumer::AssociationManager.new(store, @server_url,
  228. compat, negotiator)
  229. class << assoc_manager
  230. attr_accessor :responses
  231. def request_association(assoc_type, session_type)
  232. m = @responses.shift
  233. if m.is_a?(Message)
  234. raise ServerError.from_message(m)
  235. else
  236. return m
  237. end
  238. end
  239. end
  240. assoc_manager.responses = responses
  241. assoc_manager.negotiate_association
  242. end
  243. end
  244. # Test the session type negotiation behavior of an OpenID 2
  245. # consumer.
  246. class TestOpenID2SessionNegotiation < Test::Unit::TestCase
  247. include NegotiationTestMixin
  248. Compat = false
  249. def setup
  250. @server_url = 'http://invalid/'
  251. @openid_ns = OPENID2_NS
  252. end
  253. # Test the case where the response to an associate request is a
  254. # server error or is otherwise undecipherable.
  255. def test_bad_response
  256. assert_log_matches('Server error when requesting an association') {
  257. assert_equal(call_negotiate([mk_message({})]), nil)
  258. }
  259. end
  260. # Test the case where the association type (assoc_type) returned
  261. # in an unsupported-type response is absent.
  262. def test_empty_assoc_type
  263. msg = mk_message({'error' => 'Unsupported type',
  264. 'error_code' => 'unsupported-type',
  265. 'session_type' => 'new-session-type',
  266. })
  267. assert_log_matches('Unsupported association type',
  268. "Server #{@server_url} responded with unsupported "\
  269. "association session but did not supply a fallback."
  270. ) {
  271. assert_equal(call_negotiate([msg]), nil)
  272. }
  273. end
  274. # Test the case where the session type (session_type) returned
  275. # in an unsupported-type response is absent.
  276. def test_empty_session_type
  277. msg = mk_message({'error' => 'Unsupported type',
  278. 'error_code' => 'unsupported-type',
  279. 'assoc_type' => 'new-assoc-type',
  280. })
  281. assert_log_matches('Unsupported association type',
  282. "Server #{@server_url} responded with unsupported "\
  283. "association session but did not supply a fallback."
  284. ) {
  285. assert_equal(call_negotiate([msg]), nil)
  286. }
  287. end
  288. # Test the case where an unsupported-type response specifies a
  289. # preferred (assoc_type, session_type) combination that is not
  290. # allowed by the consumer's SessionNegotiator.
  291. def test_not_allowed
  292. negotiator = AssociationNegotiator.new([])
  293. negotiator.instance_eval{
  294. @allowed_types = [['assoc_bogus', 'session_bogus']]
  295. }
  296. msg = mk_message({'error' => 'Unsupported type',
  297. 'error_code' => 'unsupported-type',
  298. 'assoc_type' => 'not-allowed',
  299. 'session_type' => 'not-allowed',
  300. })
  301. assert_log_matches('Unsupported association type',
  302. 'Server sent unsupported session/association type:') {
  303. assert_equal(call_negotiate([msg], negotiator), nil)
  304. }
  305. end
  306. # Test the case where an unsupported-type response triggers a
  307. # retry to get an association with the new preferred type.
  308. def test_unsupported_with_retry
  309. msg = mk_message({'error' => 'Unsupported type',
  310. 'error_code' => 'unsupported-type',
  311. 'assoc_type' => 'HMAC-SHA1',
  312. 'session_type' => 'DH-SHA1',
  313. })
  314. assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
  315. assert_log_matches('Unsupported association type') {
  316. assert_equal(assoc, call_negotiate([msg, assoc]))
  317. }
  318. end
  319. # Test the case where an unsupported-typ response triggers a
  320. # retry, but the retry fails and nil is returned instead.
  321. def test_unsupported_with_retry_and_fail
  322. msg = mk_message({'error' => 'Unsupported type',
  323. 'error_code' => 'unsupported-type',
  324. 'assoc_type' => 'HMAC-SHA1',
  325. 'session_type' => 'DH-SHA1',
  326. })
  327. assert_log_matches('Unsupported association type',
  328. "Server #{@server_url} refused") {
  329. assert_equal(call_negotiate([msg, msg]), nil)
  330. }
  331. end
  332. # Test the valid case, wherein an association is returned on the
  333. # first attempt to get one.
  334. def test_valid
  335. assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
  336. assert_log_matches() {
  337. assert_equal(call_negotiate([assoc]), assoc)
  338. }
  339. end
  340. end
  341. # Tests for the OpenID 1 consumer association session behavior. See
  342. # the docs for TestOpenID2SessionNegotiation. Notice that this
  343. # class is not a subclass of the OpenID 2 tests. Instead, it uses
  344. # many of the same inputs but inspects the log messages logged with
  345. # oidutil.log. See the calls to self.failUnlessLogMatches. Some of
  346. # these tests pass openid2-style messages to the openid 1
  347. # association processing logic to be sure it ignores the extra data.
  348. class TestOpenID1SessionNegotiation < Test::Unit::TestCase
  349. include NegotiationTestMixin
  350. Compat = true
  351. def setup
  352. @server_url = 'http://invalid/'
  353. @openid_ns = OPENID1_NS
  354. end
  355. def test_bad_response
  356. assert_log_matches('Server error when requesting an association') {
  357. response = call_negotiate([mk_message({})])
  358. assert_equal(nil, response)
  359. }
  360. end
  361. def test_empty_assoc_type
  362. msg = mk_message({'error' => 'Unsupported type',
  363. 'error_code' => 'unsupported-type',
  364. 'session_type' => 'new-session-type',
  365. })
  366. assert_log_matches('Server error when requesting an association') {
  367. response = call_negotiate([msg])
  368. assert_equal(nil, response)
  369. }
  370. end
  371. def test_empty_session_type
  372. msg = mk_message({'error' => 'Unsupported type',
  373. 'error_code' => 'unsupported-type',
  374. 'assoc_type' => 'new-assoc-type',
  375. })
  376. assert_log_matches('Server error when requesting an association') {
  377. response = call_negotiate([msg])
  378. assert_equal(nil, response)
  379. }
  380. end
  381. def test_not_allowed
  382. negotiator = AssociationNegotiator.new([])
  383. negotiator.instance_eval{
  384. @allowed_types = [['assoc_bogus', 'session_bogus']]
  385. }
  386. msg = mk_message({'error' => 'Unsupported type',
  387. 'error_code' => 'unsupported-type',
  388. 'assoc_type' => 'not-allowed',
  389. 'session_type' => 'not-allowed',
  390. })
  391. assert_log_matches('Server error when requesting an association') {
  392. response = call_negotiate([msg])
  393. assert_equal(nil, response)
  394. }
  395. end
  396. def test_unsupported_with_retry
  397. msg = mk_message({'error' => 'Unsupported type',
  398. 'error_code' => 'unsupported-type',
  399. 'assoc_type' => 'HMAC-SHA1',
  400. 'session_type' => 'DH-SHA1',
  401. })
  402. assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
  403. assert_log_matches('Server error when requesting an association') {
  404. response = call_negotiate([msg, assoc])
  405. assert_equal(nil, response)
  406. }
  407. end
  408. def test_valid
  409. assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
  410. assert_log_matches() {
  411. response = call_negotiate([assoc])
  412. assert_equal(assoc, response)
  413. }
  414. end
  415. end
  416. class TestExtractAssociation < Test::Unit::TestCase
  417. include ProtocolErrorMixin
  418. # An OpenID associate response (without the namespace)
  419. DEFAULTS = {
  420. 'expires_in' => '1000',
  421. 'assoc_handle' => 'a handle',
  422. 'assoc_type' => 'a type',
  423. 'session_type' => 'a session type',
  424. }
  425. def setup
  426. @assoc_manager = Consumer::AssociationManager.new(nil, nil)
  427. end
  428. # Make tests that ensure that an association response that is
  429. # missing required fields will raise an Message::KeyNotFound.
  430. #
  431. # According to 'Association Session Response' subsection 'Common
  432. # Response Parameters', the following fields are required for
  433. # OpenID 2.0:
  434. #
  435. # * ns
  436. # * session_type
  437. # * assoc_handle
  438. # * assoc_type
  439. # * expires_in
  440. #
  441. # In OpenID 1, everything except 'session_type' and 'ns' are
  442. # required.
  443. MISSING_FIELD_SETS = ([["no_fields", []]] +
  444. (DEFAULTS.keys.map do |f|
  445. fields = DEFAULTS.keys
  446. fields.delete(f)
  447. ["missing_#{f}", fields]
  448. end)
  449. )
  450. [OPENID1_NS, OPENID2_NS].each do |ns|
  451. MISSING_FIELD_SETS.each do |name, fields|
  452. # OpenID 1 is allowed to be missing session_type
  453. if ns != OPENID1_NS and name != 'missing_session_type'
  454. test = lambda do
  455. msg = Message.new(ns)
  456. fields.each do |field|
  457. msg.set_arg(ns, field, DEFAULTS[field])
  458. end
  459. assert_raises(Message::KeyNotFound) do
  460. @assoc_manager.send(:extract_association, msg, nil)
  461. end
  462. end
  463. define_method("test_#{name}", test)
  464. end
  465. end
  466. end
  467. # assert that extracting a response that contains the given
  468. # response session type when the request was made for the given
  469. # request session type will raise a ProtocolError indicating
  470. # session type mismatch
  471. def assert_session_mismatch(req_type, resp_type, ns)
  472. # Create an association session that has "req_type" as its
  473. # session_type and no allowed_assoc_types
  474. assoc_session_class = Class.new do
  475. @session_type = req_type
  476. def self.session_type
  477. @session_type
  478. end
  479. def self.allowed_assoc_types
  480. []
  481. end
  482. end
  483. assoc_session = assoc_session_class.new
  484. # Build an OpenID 1 or 2 association response message that has
  485. # the specified association session type
  486. msg = Message.new(ns)
  487. msg.update_args(ns, DEFAULTS)
  488. msg.set_arg(ns, 'session_type', resp_type)
  489. # The request type and response type have been chosen to produce
  490. # a session type mismatch.
  491. assert_protocol_error('Session type mismatch') {
  492. @assoc_manager.send(:extract_association, msg, assoc_session)
  493. }
  494. end
  495. [['no-encryption', '', OPENID2_NS],
  496. ['DH-SHA1', 'no-encryption', OPENID2_NS],
  497. ['DH-SHA256', 'no-encryption', OPENID2_NS],
  498. ['no-encryption', 'DH-SHA1', OPENID2_NS],
  499. ['DH-SHA1', 'DH-SHA256', OPENID1_NS],
  500. ['DH-SHA256', 'DH-SHA1', OPENID1_NS],
  501. ['no-encryption', 'DH-SHA1', OPENID1_NS],
  502. ].each do |req_type, resp_type, ns|
  503. test = lambda { assert_session_mismatch(req_type, resp_type, ns) }
  504. name = "test_mismatch_req_#{req_type}_resp_#{resp_type}_#{ns}"
  505. define_method(name, test)
  506. end
  507. def test_openid1_no_encryption_fallback
  508. # A DH-SHA1 session
  509. assoc_session = Consumer::DiffieHellmanSHA1Session.new
  510. # An OpenID 1 no-encryption association response
  511. msg = Message.from_openid_args({
  512. 'expires_in' => '1000',
  513. 'assoc_handle' => 'a handle',
  514. 'assoc_type' => 'HMAC-SHA1',
  515. 'mac_key' => 'X' * 20,
  516. })
  517. # Should succeed
  518. assoc = @assoc_manager.send(:extract_association, msg, assoc_session)
  519. assert_equal('a handle', assoc.handle)
  520. assert_equal('HMAC-SHA1', assoc.assoc_type)
  521. assert(assoc.expires_in.between?(999, 1000))
  522. assert('X' * 20, assoc.secret)
  523. end
  524. end
  525. class GetOpenIDSessionTypeTest < Test::Unit::TestCase
  526. include TestUtil
  527. SERVER_URL = 'http://invalid/'
  528. def do_test(expected_session_type, session_type_value)
  529. # Create a Message with just 'session_type' in it, since
  530. # that's all this function will use. 'session_type' may be
  531. # absent if it's set to None.
  532. args = {}
  533. if !session_type_value.nil?
  534. args['session_type'] = session_type_value
  535. end
  536. message = Message.from_openid_args(args)
  537. assert(message.is_openid1)
  538. assoc_manager = Consumer::AssociationManager.new(nil, SERVER_URL)
  539. actual_session_type = assoc_manager.send(:get_openid1_session_type,
  540. message)
  541. error_message = ("Returned session type parameter #{session_type_value}"\
  542. "was expected to yield session type "\
  543. "#{expected_session_type}, but yielded "\
  544. "#{actual_session_type}")
  545. assert_equal(expected_session_type, actual_session_type, error_message)
  546. end
  547. [['nil', 'no-encryption', nil],
  548. ['empty', 'no-encryption', ''],
  549. ['dh_sha1', 'DH-SHA1', 'DH-SHA1'],
  550. ['dh_sha256', 'DH-SHA256', 'DH-SHA256'],
  551. ].each {|name, expected, input|
  552. # Define a test method that will check what session type will be
  553. # used if the OpenID 1 response to an associate call sets the
  554. # 'session_type' field to `session_type_value`
  555. test = lambda {assert_log_matches() { do_test(expected, input) } }
  556. define_method("test_#{name}", &test)
  557. }
  558. # This one's different because it expects log messages
  559. def test_explicit_no_encryption
  560. assert_log_matches("WARNING: #{SERVER_URL} sent 'no-encryption'"){
  561. do_test('no-encryption', 'no-encryption')
  562. }
  563. end
  564. end
  565. class ExtractAssociationTest < Test::Unit::TestCase
  566. include ProtocolErrorMixin
  567. SERVER_URL = 'http://invalid/'
  568. def setup
  569. @session_type = 'testing-session'
  570. # This must something that works for Association::from_expires_in
  571. @assoc_type = 'HMAC-SHA1'
  572. @assoc_handle = 'testing-assoc-handle'
  573. # These arguments should all be valid
  574. @assoc_response =
  575. Message.from_openid_args({
  576. 'expires_in' => '1000',
  577. 'assoc_handle' => @assoc_handle,
  578. 'assoc_type' => @assoc_type,
  579. 'session_type' => @session_type,
  580. 'ns' => OPENID2_NS,
  581. })
  582. assoc_session_cls = Class.new do
  583. class << self
  584. attr_accessor :allowed_assoc_types, :session_type
  585. end
  586. attr_reader :extract_secret_called, :secret
  587. def initialize
  588. @extract_secret_called = false
  589. @secret = 'shhhhh!'
  590. end
  591. def extract_secret(_)
  592. @extract_secret_called = true
  593. @secret
  594. end
  595. end
  596. @assoc_session = assoc_session_cls.new
  597. @assoc_session.class.allowed_assoc_types = [@assoc_type]
  598. @assoc_session.class.session_type = @session_type
  599. @assoc_manager = Consumer::AssociationManager.new(nil, SERVER_URL)
  600. end
  601. def call_extract
  602. @assoc_manager.send(:extract_association,
  603. @assoc_response, @assoc_session)
  604. end
  605. # Handle a full successful association response
  606. def test_works_with_good_fields
  607. assoc = call_extract
  608. assert(@assoc_session.extract_secret_called)
  609. assert_equal(@assoc_session.secret, assoc.secret)
  610. assert_equal(1000, assoc.lifetime)
  611. assert_equal(@assoc_handle, assoc.handle)
  612. assert_equal(@assoc_type, assoc.assoc_type)
  613. end
  614. def test_bad_assoc_type
  615. # Make sure that the assoc type in the response is not valid
  616. # for the given session.
  617. @assoc_session.class.allowed_assoc_types = []
  618. assert_protocol_error('Unsupported assoc_type for sess') {call_extract}
  619. end
  620. def test_bad_expires_in
  621. # Invalid value for expires_in should cause failure
  622. @assoc_response.set_arg(OPENID_NS, 'expires_in', 'forever')
  623. assert_protocol_error('Invalid expires_in') {call_extract}
  624. end
  625. end
  626. class TestExtractAssociationDiffieHellman < Test::Unit::TestCase
  627. include ProtocolErrorMixin
  628. SECRET = 'x' * 20
  629. def setup
  630. @assoc_manager = Consumer::AssociationManager.new(nil, nil)
  631. end
  632. def setup_dh
  633. sess, message = @assoc_manager.send(:create_associate_request,
  634. 'HMAC-SHA1', 'DH-SHA1')
  635. server_dh = DiffieHellman.new
  636. cons_dh = sess.instance_variable_get('@dh')
  637. enc_mac_key = server_dh.xor_secret(CryptUtil.method(:sha1),
  638. cons_dh.public, SECRET)
  639. server_resp = {
  640. 'dh_server_public' => CryptUtil.num_to_base64(server_dh.public),
  641. 'enc_mac_key' => Util.to_base64(enc_mac_key),
  642. 'assoc_type' => 'HMAC-SHA1',
  643. 'assoc_handle' => 'handle',
  644. 'expires_in' => '1000',
  645. 'session_type' => 'DH-SHA1',
  646. }
  647. if @assoc_manager.instance_variable_get(:@compatibility_mode)
  648. server_resp['ns'] = OPENID2_NS
  649. end
  650. return [sess, Message.from_openid_args(server_resp)]
  651. end
  652. def test_success
  653. sess, server_resp = setup_dh
  654. ret = @assoc_manager.send(:extract_association, server_resp, sess)
  655. assert(!ret.nil?)
  656. assert_equal(ret.assoc_type, 'HMAC-SHA1')
  657. assert_equal(ret.secret, SECRET)
  658. assert_equal(ret.handle, 'handle')
  659. assert_equal(ret.lifetime, 1000)
  660. end
  661. def test_openid2success
  662. # Use openid 1 type in endpoint so _setUpDH checks
  663. # compatibility mode state properly
  664. @assoc_manager.instance_variable_set('@compatibility_mode', true)
  665. test_success()
  666. end
  667. def test_bad_dh_values
  668. sess, server_resp = setup_dh
  669. server_resp.set_arg(OPENID_NS, 'enc_mac_key', '\x00\x00\x00')
  670. assert_protocol_error('Malformed response for') {
  671. @assoc_manager.send(:extract_association, server_resp, sess)
  672. }
  673. end
  674. end
  675. class TestAssocManagerGetAssociation < Test::Unit::TestCase
  676. include FetcherMixin
  677. include TestUtil
  678. attr_reader :negotiate_association
  679. def setup
  680. @server_url = 'http://invalid/'
  681. @store = Store::Memory.new
  682. @assoc_manager = Consumer::AssociationManager.new(@store, @server_url)
  683. @assoc_manager.extend(Const)
  684. @assoc = Association.new('handle', 'secret', Time.now, 10000,
  685. 'HMAC-SHA1')
  686. end
  687. def set_negotiate_response(assoc)
  688. @assoc_manager.const(:negotiate_association, assoc)
  689. end
  690. def test_not_in_store_no_response
  691. set_negotiate_response(nil)
  692. assert_equal(nil, @assoc_manager.get_association)
  693. end
  694. def test_not_in_store_negotiate_assoc
  695. # Not stored beforehand:
  696. stored_assoc = @store.get_association(@server_url, @assoc.handle)
  697. assert_equal(nil, stored_assoc)
  698. # Returned from associate call:
  699. set_negotiate_response(@assoc)
  700. assert_equal(@assoc, @assoc_manager.get_association)
  701. # It should have been stored:
  702. stored_assoc = @store.get_association(@server_url, @assoc.handle)
  703. assert_equal(@assoc, stored_assoc)
  704. end
  705. def test_in_store_no_response
  706. set_negotiate_response(nil)
  707. @store.store_association(@server_url, @assoc)
  708. assert_equal(@assoc, @assoc_manager.get_association)
  709. end
  710. def test_request_assoc_with_status_error
  711. fetcher_class = Class.new do
  712. define_method(:fetch) do |*args|
  713. MockResponse.new(500, '')
  714. end
  715. end
  716. with_fetcher(fetcher_class.new) do
  717. assert_log_matches('Got HTTP status error when requesting') {
  718. result = @assoc_manager.send(:request_association, 'HMAC-SHA1',
  719. 'no-encryption')
  720. assert(result.nil?)
  721. }
  722. end
  723. end
  724. end
  725. class TestAssocManagerRequestAssociation < Test::Unit::TestCase
  726. include FetcherMixin
  727. include TestUtil
  728. def setup
  729. @assoc_manager = Consumer::AssociationManager.new(nil, 'http://invalid/')
  730. @assoc_type = 'HMAC-SHA1'
  731. @session_type = 'no-encryption'
  732. @message = Message.new(OPENID2_NS)
  733. @message.update_args(OPENID_NS, {
  734. 'assoc_type' => @assoc_type,
  735. 'session_type' => @session_type,
  736. 'assoc_handle' => 'kaboodle',
  737. 'expires_in' => '1000',
  738. 'mac_key' => 'X' * 20,
  739. })
  740. end
  741. def make_request
  742. kv = @message.to_kvform
  743. fetcher_class = Class.new do
  744. define_method(:fetch) do |*args|
  745. MockResponse.new(200, kv)
  746. end
  747. end
  748. with_fetcher(fetcher_class.new) do
  749. @assoc_manager.send(:request_association, @assoc_type, @session_type)
  750. end
  751. end
  752. # The association we get is from valid processing of our result,
  753. # and that no errors are raised
  754. def test_success
  755. assert_equal('kaboodle', make_request.handle)
  756. end
  757. # A missing parameter gets translated into a log message and
  758. # causes the method to return nil
  759. def test_missing_fields
  760. @message.del_arg(OPENID_NS, 'assoc_type')
  761. assert_log_matches('Missing required par') {
  762. assert_equal(nil, make_request)
  763. }
  764. end
  765. # A bad value results in a log message and causes the method to
  766. # return nil
  767. def test_protocol_error
  768. @message.set_arg(OPENID_NS, 'expires_in', 'goats')
  769. assert_log_matches('Protocol error processing') {
  770. assert_equal(nil, make_request)
  771. }
  772. end
  773. end
  774. end