918 lines
30 KiB
Ruby
918 lines
30 KiB
Ruby
require "openid/consumer/associationmanager"
|
|
require "openid/association"
|
|
require "openid/dh"
|
|
require "openid/util"
|
|
require "openid/cryptutil"
|
|
require "openid/message"
|
|
require "openid/store/memory"
|
|
require "test/unit"
|
|
require "util"
|
|
require "time"
|
|
|
|
module OpenID
|
|
class DHAssocSessionTest < Test::Unit::TestCase
|
|
def test_sha1_get_request
|
|
# Initialized without an explicit DH gets defaults
|
|
sess = Consumer::DiffieHellmanSHA1Session.new
|
|
assert_equal(['dh_consumer_public'], sess.get_request.keys)
|
|
assert_nothing_raised do
|
|
Util::from_base64(sess.get_request['dh_consumer_public'])
|
|
end
|
|
end
|
|
|
|
def test_sha1_get_request_custom_dh
|
|
dh = DiffieHellman.new(1299721, 2)
|
|
sess = Consumer::DiffieHellmanSHA1Session.new(dh)
|
|
req = sess.get_request
|
|
assert_equal(['dh_consumer_public', 'dh_modulus', 'dh_gen'].sort,
|
|
req.keys.sort)
|
|
assert_equal(dh.modulus, CryptUtil.base64_to_num(req['dh_modulus']))
|
|
assert_equal(dh.generator, CryptUtil.base64_to_num(req['dh_gen']))
|
|
assert_nothing_raised do
|
|
Util::from_base64(req['dh_consumer_public'])
|
|
end
|
|
end
|
|
end
|
|
|
|
module TestDiffieHellmanResponseParametersMixin
|
|
def setup
|
|
session_cls = self.class.session_cls
|
|
|
|
# Pre-compute DH with small prime so tests run quickly.
|
|
@server_dh = DiffieHellman.new(100389557, 2)
|
|
@consumer_dh = DiffieHellman.new(100389557, 2)
|
|
|
|
# base64(btwoc(g ^ xb mod p))
|
|
@dh_server_public = CryptUtil.num_to_base64(@server_dh.public)
|
|
|
|
@secret = CryptUtil.random_string(session_cls.secret_size)
|
|
|
|
enc_mac_key_unencoded =
|
|
@server_dh.xor_secret(session_cls.hashfunc,
|
|
@consumer_dh.public,
|
|
@secret)
|
|
|
|
@enc_mac_key = Util.to_base64(enc_mac_key_unencoded)
|
|
|
|
@consumer_session = session_cls.new(@consumer_dh)
|
|
|
|
@msg = Message.new(self.class.message_namespace)
|
|
end
|
|
|
|
def test_extract_secret
|
|
@msg.set_arg(OPENID_NS, 'dh_server_public', @dh_server_public)
|
|
@msg.set_arg(OPENID_NS, 'enc_mac_key', @enc_mac_key)
|
|
|
|
extracted = @consumer_session.extract_secret(@msg)
|
|
assert_equal(extracted, @secret)
|
|
end
|
|
|
|
def test_absent_serve_public
|
|
@msg.set_arg(OPENID_NS, 'enc_mac_key', @enc_mac_key)
|
|
|
|
assert_raises(Message::KeyNotFound) {
|
|
@consumer_session.extract_secret(@msg)
|
|
}
|
|
end
|
|
|
|
def test_absent_mac_key
|
|
@msg.set_arg(OPENID_NS, 'dh_server_public', @dh_server_public)
|
|
|
|
assert_raises(Message::KeyNotFound) {
|
|
@consumer_session.extract_secret(@msg)
|
|
}
|
|
end
|
|
|
|
def test_invalid_base64_public
|
|
@msg.set_arg(OPENID_NS, 'dh_server_public', 'n o t b a s e 6 4.')
|
|
@msg.set_arg(OPENID_NS, 'enc_mac_key', @enc_mac_key)
|
|
|
|
assert_raises(ArgumentError) {
|
|
@consumer_session.extract_secret(@msg)
|
|
}
|
|
end
|
|
|
|
def test_invalid_base64_mac_key
|
|
@msg.set_arg(OPENID_NS, 'dh_server_public', @dh_server_public)
|
|
@msg.set_arg(OPENID_NS, 'enc_mac_key', 'n o t base 64')
|
|
|
|
assert_raises(ArgumentError) {
|
|
@consumer_session.extract_secret(@msg)
|
|
}
|
|
end
|
|
end
|
|
|
|
class TestConsumerOpenID1DHSHA1 < Test::Unit::TestCase
|
|
include TestDiffieHellmanResponseParametersMixin
|
|
class << self
|
|
attr_reader :session_cls, :message_namespace
|
|
end
|
|
|
|
@session_cls = Consumer::DiffieHellmanSHA1Session
|
|
@message_namespace = OPENID1_NS
|
|
end
|
|
|
|
class TestConsumerOpenID2DHSHA1 < Test::Unit::TestCase
|
|
include TestDiffieHellmanResponseParametersMixin
|
|
class << self
|
|
attr_reader :session_cls, :message_namespace
|
|
end
|
|
|
|
@session_cls = Consumer::DiffieHellmanSHA1Session
|
|
@message_namespace = OPENID2_NS
|
|
end
|
|
|
|
class TestConsumerOpenID2DHSHA256 < Test::Unit::TestCase
|
|
include TestDiffieHellmanResponseParametersMixin
|
|
class << self
|
|
attr_reader :session_cls, :message_namespace
|
|
end
|
|
|
|
@session_cls = Consumer::DiffieHellmanSHA256Session
|
|
@message_namespace = OPENID2_NS
|
|
end
|
|
|
|
class TestConsumerNoEncryptionSession < Test::Unit::TestCase
|
|
def setup
|
|
@sess = Consumer::NoEncryptionSession.new
|
|
end
|
|
|
|
def test_empty_request
|
|
assert_equal(@sess.get_request, {})
|
|
end
|
|
|
|
def test_get_secret
|
|
secret = 'shhh!' * 4
|
|
mac_key = Util.to_base64(secret)
|
|
msg = Message.from_openid_args({'mac_key' => mac_key})
|
|
assert_equal(secret, @sess.extract_secret(msg))
|
|
end
|
|
end
|
|
|
|
class TestCreateAssociationRequest < Test::Unit::TestCase
|
|
def setup
|
|
@server_url = 'http://invalid/'
|
|
@assoc_manager = Consumer::AssociationManager.new(nil, @server_url)
|
|
class << @assoc_manager
|
|
def compatibility_mode=(val)
|
|
@compatibility_mode = val
|
|
end
|
|
end
|
|
@assoc_type = 'HMAC-SHA1'
|
|
end
|
|
|
|
def test_no_encryption_sends_type
|
|
session_type = 'no-encryption'
|
|
session, args = @assoc_manager.send(:create_associate_request,
|
|
@assoc_type,
|
|
session_type)
|
|
|
|
assert(session.is_a?(Consumer::NoEncryptionSession))
|
|
expected = Message.from_openid_args(
|
|
{'ns' => OPENID2_NS,
|
|
'session_type' => session_type,
|
|
'mode' => 'associate',
|
|
'assoc_type' => @assoc_type,
|
|
})
|
|
|
|
assert_equal(expected, args)
|
|
end
|
|
|
|
def test_no_encryption_compatibility
|
|
@assoc_manager.compatibility_mode = true
|
|
session_type = 'no-encryption'
|
|
session, args = @assoc_manager.send(:create_associate_request,
|
|
@assoc_type,
|
|
session_type)
|
|
|
|
assert(session.is_a?(Consumer::NoEncryptionSession))
|
|
assert_equal(Message.from_openid_args({'mode' => 'associate',
|
|
'assoc_type' => @assoc_type,
|
|
}), args)
|
|
end
|
|
|
|
def test_dh_sha1_compatibility
|
|
@assoc_manager.compatibility_mode = true
|
|
session_type = 'DH-SHA1'
|
|
session, args = @assoc_manager.send(:create_associate_request,
|
|
@assoc_type,
|
|
session_type)
|
|
|
|
|
|
assert(session.is_a?(Consumer::DiffieHellmanSHA1Session))
|
|
|
|
# This is a random base-64 value, so just check that it's
|
|
# present.
|
|
assert_not_nil(args.get_arg(OPENID1_NS, 'dh_consumer_public'))
|
|
args.del_arg(OPENID1_NS, 'dh_consumer_public')
|
|
|
|
# OK, session_type is set here and not for no-encryption
|
|
# compatibility
|
|
expected = Message.from_openid_args({'mode' => 'associate',
|
|
'session_type' => 'DH-SHA1',
|
|
'assoc_type' => @assoc_type,
|
|
})
|
|
assert_equal(expected, args)
|
|
end
|
|
end
|
|
|
|
class TestAssociationManagerExpiresIn < Test::Unit::TestCase
|
|
def expires_in_msg(val)
|
|
msg = Message.from_openid_args({'expires_in' => val})
|
|
Consumer::AssociationManager.extract_expires_in(msg)
|
|
end
|
|
|
|
def test_parse_fail
|
|
['',
|
|
'-2',
|
|
' 1',
|
|
' ',
|
|
'0x00',
|
|
'foosball',
|
|
'1\n',
|
|
'100,000,000,000',
|
|
].each do |x|
|
|
assert_raises(ProtocolError) {expires_in_msg(x)}
|
|
end
|
|
end
|
|
|
|
def test_parse
|
|
['0',
|
|
'1',
|
|
'1000',
|
|
'9999999',
|
|
'01',
|
|
].each do |n|
|
|
assert_equal(n.to_i, expires_in_msg(n))
|
|
end
|
|
end
|
|
end
|
|
|
|
class TestAssociationManagerCreateSession < Test::Unit::TestCase
|
|
def test_invalid
|
|
assert_raises(ArgumentError) {
|
|
Consumer::AssociationManager.create_session('monkeys')
|
|
}
|
|
end
|
|
|
|
def test_sha256
|
|
sess = Consumer::AssociationManager.create_session('DH-SHA256')
|
|
assert(sess.is_a?(Consumer::DiffieHellmanSHA256Session))
|
|
end
|
|
end
|
|
|
|
module NegotiationTestMixin
|
|
include TestUtil
|
|
def mk_message(args)
|
|
args['ns'] = @openid_ns
|
|
Message.from_openid_args(args)
|
|
end
|
|
|
|
def call_negotiate(responses, negotiator=nil)
|
|
store = nil
|
|
compat = self.class::Compat
|
|
assoc_manager = Consumer::AssociationManager.new(store, @server_url,
|
|
compat, negotiator)
|
|
class << assoc_manager
|
|
attr_accessor :responses
|
|
|
|
def request_association(assoc_type, session_type)
|
|
m = @responses.shift
|
|
if m.is_a?(Message)
|
|
raise ServerError.from_message(m)
|
|
else
|
|
return m
|
|
end
|
|
end
|
|
end
|
|
assoc_manager.responses = responses
|
|
assoc_manager.negotiate_association
|
|
end
|
|
end
|
|
|
|
# Test the session type negotiation behavior of an OpenID 2
|
|
# consumer.
|
|
class TestOpenID2SessionNegotiation < Test::Unit::TestCase
|
|
include NegotiationTestMixin
|
|
|
|
Compat = false
|
|
|
|
def setup
|
|
@server_url = 'http://invalid/'
|
|
@openid_ns = OPENID2_NS
|
|
end
|
|
|
|
# Test the case where the response to an associate request is a
|
|
# server error or is otherwise undecipherable.
|
|
def test_bad_response
|
|
assert_log_matches('Server error when requesting an association') {
|
|
assert_equal(call_negotiate([mk_message({})]), nil)
|
|
}
|
|
end
|
|
|
|
# Test the case where the association type (assoc_type) returned
|
|
# in an unsupported-type response is absent.
|
|
def test_empty_assoc_type
|
|
msg = mk_message({'error' => 'Unsupported type',
|
|
'error_code' => 'unsupported-type',
|
|
'session_type' => 'new-session-type',
|
|
})
|
|
|
|
assert_log_matches('Unsupported association type',
|
|
"Server #{@server_url} responded with unsupported "\
|
|
"association session but did not supply a fallback."
|
|
) {
|
|
assert_equal(call_negotiate([msg]), nil)
|
|
}
|
|
|
|
end
|
|
|
|
# Test the case where the session type (session_type) returned
|
|
# in an unsupported-type response is absent.
|
|
def test_empty_session_type
|
|
msg = mk_message({'error' => 'Unsupported type',
|
|
'error_code' => 'unsupported-type',
|
|
'assoc_type' => 'new-assoc-type',
|
|
})
|
|
|
|
assert_log_matches('Unsupported association type',
|
|
"Server #{@server_url} responded with unsupported "\
|
|
"association session but did not supply a fallback."
|
|
) {
|
|
assert_equal(call_negotiate([msg]), nil)
|
|
}
|
|
end
|
|
|
|
# Test the case where an unsupported-type response specifies a
|
|
# preferred (assoc_type, session_type) combination that is not
|
|
# allowed by the consumer's SessionNegotiator.
|
|
def test_not_allowed
|
|
negotiator = AssociationNegotiator.new([])
|
|
negotiator.instance_eval{
|
|
@allowed_types = [['assoc_bogus', 'session_bogus']]
|
|
}
|
|
msg = mk_message({'error' => 'Unsupported type',
|
|
'error_code' => 'unsupported-type',
|
|
'assoc_type' => 'not-allowed',
|
|
'session_type' => 'not-allowed',
|
|
})
|
|
|
|
assert_log_matches('Unsupported association type',
|
|
'Server sent unsupported session/association type:') {
|
|
assert_equal(call_negotiate([msg], negotiator), nil)
|
|
}
|
|
end
|
|
|
|
# Test the case where an unsupported-type response triggers a
|
|
# retry to get an association with the new preferred type.
|
|
def test_unsupported_with_retry
|
|
msg = mk_message({'error' => 'Unsupported type',
|
|
'error_code' => 'unsupported-type',
|
|
'assoc_type' => 'HMAC-SHA1',
|
|
'session_type' => 'DH-SHA1',
|
|
})
|
|
|
|
assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
|
|
|
|
assert_log_matches('Unsupported association type') {
|
|
assert_equal(assoc, call_negotiate([msg, assoc]))
|
|
}
|
|
end
|
|
|
|
# Test the case where an unsupported-typ response triggers a
|
|
# retry, but the retry fails and nil is returned instead.
|
|
def test_unsupported_with_retry_and_fail
|
|
msg = mk_message({'error' => 'Unsupported type',
|
|
'error_code' => 'unsupported-type',
|
|
'assoc_type' => 'HMAC-SHA1',
|
|
'session_type' => 'DH-SHA1',
|
|
})
|
|
|
|
assert_log_matches('Unsupported association type',
|
|
"Server #{@server_url} refused") {
|
|
assert_equal(call_negotiate([msg, msg]), nil)
|
|
}
|
|
end
|
|
|
|
# Test the valid case, wherein an association is returned on the
|
|
# first attempt to get one.
|
|
def test_valid
|
|
assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
|
|
|
|
assert_log_matches() {
|
|
assert_equal(call_negotiate([assoc]), assoc)
|
|
}
|
|
end
|
|
end
|
|
|
|
|
|
# Tests for the OpenID 1 consumer association session behavior. See
|
|
# the docs for TestOpenID2SessionNegotiation. Notice that this
|
|
# class is not a subclass of the OpenID 2 tests. Instead, it uses
|
|
# many of the same inputs but inspects the log messages logged with
|
|
# oidutil.log. See the calls to self.failUnlessLogMatches. Some of
|
|
# these tests pass openid2-style messages to the openid 1
|
|
# association processing logic to be sure it ignores the extra data.
|
|
class TestOpenID1SessionNegotiation < Test::Unit::TestCase
|
|
include NegotiationTestMixin
|
|
|
|
Compat = true
|
|
|
|
def setup
|
|
@server_url = 'http://invalid/'
|
|
@openid_ns = OPENID1_NS
|
|
end
|
|
|
|
def test_bad_response
|
|
assert_log_matches('Server error when requesting an association') {
|
|
response = call_negotiate([mk_message({})])
|
|
assert_equal(nil, response)
|
|
}
|
|
end
|
|
|
|
def test_empty_assoc_type
|
|
msg = mk_message({'error' => 'Unsupported type',
|
|
'error_code' => 'unsupported-type',
|
|
'session_type' => 'new-session-type',
|
|
})
|
|
|
|
assert_log_matches('Server error when requesting an association') {
|
|
response = call_negotiate([msg])
|
|
assert_equal(nil, response)
|
|
}
|
|
end
|
|
|
|
def test_empty_session_type
|
|
msg = mk_message({'error' => 'Unsupported type',
|
|
'error_code' => 'unsupported-type',
|
|
'assoc_type' => 'new-assoc-type',
|
|
})
|
|
|
|
assert_log_matches('Server error when requesting an association') {
|
|
response = call_negotiate([msg])
|
|
assert_equal(nil, response)
|
|
}
|
|
end
|
|
|
|
def test_not_allowed
|
|
negotiator = AssociationNegotiator.new([])
|
|
negotiator.instance_eval{
|
|
@allowed_types = [['assoc_bogus', 'session_bogus']]
|
|
}
|
|
|
|
msg = mk_message({'error' => 'Unsupported type',
|
|
'error_code' => 'unsupported-type',
|
|
'assoc_type' => 'not-allowed',
|
|
'session_type' => 'not-allowed',
|
|
})
|
|
|
|
assert_log_matches('Server error when requesting an association') {
|
|
response = call_negotiate([msg])
|
|
assert_equal(nil, response)
|
|
}
|
|
end
|
|
|
|
def test_unsupported_with_retry
|
|
msg = mk_message({'error' => 'Unsupported type',
|
|
'error_code' => 'unsupported-type',
|
|
'assoc_type' => 'HMAC-SHA1',
|
|
'session_type' => 'DH-SHA1',
|
|
})
|
|
|
|
assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
|
|
|
|
|
|
assert_log_matches('Server error when requesting an association') {
|
|
response = call_negotiate([msg, assoc])
|
|
assert_equal(nil, response)
|
|
}
|
|
end
|
|
|
|
def test_valid
|
|
assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
|
|
assert_log_matches() {
|
|
response = call_negotiate([assoc])
|
|
assert_equal(assoc, response)
|
|
}
|
|
end
|
|
end
|
|
|
|
|
|
class TestExtractAssociation < Test::Unit::TestCase
|
|
include ProtocolErrorMixin
|
|
|
|
# An OpenID associate response (without the namespace)
|
|
DEFAULTS = {
|
|
'expires_in' => '1000',
|
|
'assoc_handle' => 'a handle',
|
|
'assoc_type' => 'a type',
|
|
'session_type' => 'a session type',
|
|
}
|
|
|
|
def setup
|
|
@assoc_manager = Consumer::AssociationManager.new(nil, nil)
|
|
end
|
|
|
|
# Make tests that ensure that an association response that is
|
|
# missing required fields will raise an Message::KeyNotFound.
|
|
#
|
|
# According to 'Association Session Response' subsection 'Common
|
|
# Response Parameters', the following fields are required for
|
|
# OpenID 2.0:
|
|
#
|
|
# * ns
|
|
# * session_type
|
|
# * assoc_handle
|
|
# * assoc_type
|
|
# * expires_in
|
|
#
|
|
# In OpenID 1, everything except 'session_type' and 'ns' are
|
|
# required.
|
|
MISSING_FIELD_SETS = ([["no_fields", []]] +
|
|
(DEFAULTS.keys.map do |f|
|
|
fields = DEFAULTS.keys
|
|
fields.delete(f)
|
|
["missing_#{f}", fields]
|
|
end)
|
|
)
|
|
|
|
[OPENID1_NS, OPENID2_NS].each do |ns|
|
|
MISSING_FIELD_SETS.each do |name, fields|
|
|
# OpenID 1 is allowed to be missing session_type
|
|
if ns != OPENID1_NS and name != 'missing_session_type'
|
|
test = lambda do
|
|
msg = Message.new(ns)
|
|
fields.each do |field|
|
|
msg.set_arg(ns, field, DEFAULTS[field])
|
|
end
|
|
assert_raises(Message::KeyNotFound) do
|
|
@assoc_manager.send(:extract_association, msg, nil)
|
|
end
|
|
end
|
|
define_method("test_#{name}", test)
|
|
end
|
|
end
|
|
end
|
|
|
|
# assert that extracting a response that contains the given
|
|
# response session type when the request was made for the given
|
|
# request session type will raise a ProtocolError indicating
|
|
# session type mismatch
|
|
def assert_session_mismatch(req_type, resp_type, ns)
|
|
# Create an association session that has "req_type" as its
|
|
# session_type and no allowed_assoc_types
|
|
assoc_session_class = Class.new do
|
|
@session_type = req_type
|
|
def self.session_type
|
|
@session_type
|
|
end
|
|
def self.allowed_assoc_types
|
|
[]
|
|
end
|
|
end
|
|
assoc_session = assoc_session_class.new
|
|
|
|
# Build an OpenID 1 or 2 association response message that has
|
|
# the specified association session type
|
|
msg = Message.new(ns)
|
|
msg.update_args(ns, DEFAULTS)
|
|
msg.set_arg(ns, 'session_type', resp_type)
|
|
|
|
# The request type and response type have been chosen to produce
|
|
# a session type mismatch.
|
|
assert_protocol_error('Session type mismatch') {
|
|
@assoc_manager.send(:extract_association, msg, assoc_session)
|
|
}
|
|
end
|
|
|
|
[['no-encryption', '', OPENID2_NS],
|
|
['DH-SHA1', 'no-encryption', OPENID2_NS],
|
|
['DH-SHA256', 'no-encryption', OPENID2_NS],
|
|
['no-encryption', 'DH-SHA1', OPENID2_NS],
|
|
['DH-SHA1', 'DH-SHA256', OPENID1_NS],
|
|
['DH-SHA256', 'DH-SHA1', OPENID1_NS],
|
|
['no-encryption', 'DH-SHA1', OPENID1_NS],
|
|
].each do |req_type, resp_type, ns|
|
|
test = lambda { assert_session_mismatch(req_type, resp_type, ns) }
|
|
name = "test_mismatch_req_#{req_type}_resp_#{resp_type}_#{ns}"
|
|
define_method(name, test)
|
|
end
|
|
|
|
def test_openid1_no_encryption_fallback
|
|
# A DH-SHA1 session
|
|
assoc_session = Consumer::DiffieHellmanSHA1Session.new
|
|
|
|
# An OpenID 1 no-encryption association response
|
|
msg = Message.from_openid_args({
|
|
'expires_in' => '1000',
|
|
'assoc_handle' => 'a handle',
|
|
'assoc_type' => 'HMAC-SHA1',
|
|
'mac_key' => 'X' * 20,
|
|
})
|
|
|
|
# Should succeed
|
|
assoc = @assoc_manager.send(:extract_association, msg, assoc_session)
|
|
assert_equal('a handle', assoc.handle)
|
|
assert_equal('HMAC-SHA1', assoc.assoc_type)
|
|
assert(assoc.expires_in.between?(999, 1000))
|
|
assert('X' * 20, assoc.secret)
|
|
end
|
|
end
|
|
|
|
class GetOpenIDSessionTypeTest < Test::Unit::TestCase
|
|
include TestUtil
|
|
|
|
SERVER_URL = 'http://invalid/'
|
|
|
|
def do_test(expected_session_type, session_type_value)
|
|
# Create a Message with just 'session_type' in it, since
|
|
# that's all this function will use. 'session_type' may be
|
|
# absent if it's set to None.
|
|
args = {}
|
|
if !session_type_value.nil?
|
|
args['session_type'] = session_type_value
|
|
end
|
|
message = Message.from_openid_args(args)
|
|
assert(message.is_openid1)
|
|
|
|
assoc_manager = Consumer::AssociationManager.new(nil, SERVER_URL)
|
|
actual_session_type = assoc_manager.send(:get_openid1_session_type,
|
|
message)
|
|
error_message = ("Returned session type parameter #{session_type_value}"\
|
|
"was expected to yield session type "\
|
|
"#{expected_session_type}, but yielded "\
|
|
"#{actual_session_type}")
|
|
assert_equal(expected_session_type, actual_session_type, error_message)
|
|
end
|
|
|
|
|
|
[['nil', 'no-encryption', nil],
|
|
['empty', 'no-encryption', ''],
|
|
['dh_sha1', 'DH-SHA1', 'DH-SHA1'],
|
|
['dh_sha256', 'DH-SHA256', 'DH-SHA256'],
|
|
].each {|name, expected, input|
|
|
# Define a test method that will check what session type will be
|
|
# used if the OpenID 1 response to an associate call sets the
|
|
# 'session_type' field to `session_type_value`
|
|
test = lambda {assert_log_matches() { do_test(expected, input) } }
|
|
define_method("test_#{name}", &test)
|
|
}
|
|
|
|
# This one's different because it expects log messages
|
|
def test_explicit_no_encryption
|
|
assert_log_matches("WARNING: #{SERVER_URL} sent 'no-encryption'"){
|
|
do_test('no-encryption', 'no-encryption')
|
|
}
|
|
end
|
|
end
|
|
|
|
class ExtractAssociationTest < Test::Unit::TestCase
|
|
include ProtocolErrorMixin
|
|
|
|
SERVER_URL = 'http://invalid/'
|
|
|
|
def setup
|
|
@session_type = 'testing-session'
|
|
|
|
# This must something that works for Association::from_expires_in
|
|
@assoc_type = 'HMAC-SHA1'
|
|
|
|
@assoc_handle = 'testing-assoc-handle'
|
|
|
|
# These arguments should all be valid
|
|
@assoc_response =
|
|
Message.from_openid_args({
|
|
'expires_in' => '1000',
|
|
'assoc_handle' => @assoc_handle,
|
|
'assoc_type' => @assoc_type,
|
|
'session_type' => @session_type,
|
|
'ns' => OPENID2_NS,
|
|
})
|
|
assoc_session_cls = Class.new do
|
|
class << self
|
|
attr_accessor :allowed_assoc_types, :session_type
|
|
end
|
|
|
|
attr_reader :extract_secret_called, :secret
|
|
def initialize
|
|
@extract_secret_called = false
|
|
@secret = 'shhhhh!'
|
|
end
|
|
|
|
def extract_secret(_)
|
|
@extract_secret_called = true
|
|
@secret
|
|
end
|
|
end
|
|
@assoc_session = assoc_session_cls.new
|
|
@assoc_session.class.allowed_assoc_types = [@assoc_type]
|
|
@assoc_session.class.session_type = @session_type
|
|
|
|
@assoc_manager = Consumer::AssociationManager.new(nil, SERVER_URL)
|
|
end
|
|
|
|
def call_extract
|
|
@assoc_manager.send(:extract_association,
|
|
@assoc_response, @assoc_session)
|
|
end
|
|
|
|
# Handle a full successful association response
|
|
def test_works_with_good_fields
|
|
assoc = call_extract
|
|
assert(@assoc_session.extract_secret_called)
|
|
assert_equal(@assoc_session.secret, assoc.secret)
|
|
assert_equal(1000, assoc.lifetime)
|
|
assert_equal(@assoc_handle, assoc.handle)
|
|
assert_equal(@assoc_type, assoc.assoc_type)
|
|
end
|
|
|
|
def test_bad_assoc_type
|
|
# Make sure that the assoc type in the response is not valid
|
|
# for the given session.
|
|
@assoc_session.class.allowed_assoc_types = []
|
|
assert_protocol_error('Unsupported assoc_type for sess') {call_extract}
|
|
end
|
|
|
|
def test_bad_expires_in
|
|
# Invalid value for expires_in should cause failure
|
|
@assoc_response.set_arg(OPENID_NS, 'expires_in', 'forever')
|
|
assert_protocol_error('Invalid expires_in') {call_extract}
|
|
end
|
|
end
|
|
|
|
class TestExtractAssociationDiffieHellman < Test::Unit::TestCase
|
|
include ProtocolErrorMixin
|
|
|
|
SECRET = 'x' * 20
|
|
|
|
def setup
|
|
@assoc_manager = Consumer::AssociationManager.new(nil, nil)
|
|
end
|
|
|
|
def setup_dh
|
|
sess, message = @assoc_manager.send(:create_associate_request,
|
|
'HMAC-SHA1', 'DH-SHA1')
|
|
|
|
server_dh = DiffieHellman.new
|
|
cons_dh = sess.instance_variable_get('@dh')
|
|
|
|
enc_mac_key = server_dh.xor_secret(CryptUtil.method(:sha1),
|
|
cons_dh.public, SECRET)
|
|
|
|
server_resp = {
|
|
'dh_server_public' => CryptUtil.num_to_base64(server_dh.public),
|
|
'enc_mac_key' => Util.to_base64(enc_mac_key),
|
|
'assoc_type' => 'HMAC-SHA1',
|
|
'assoc_handle' => 'handle',
|
|
'expires_in' => '1000',
|
|
'session_type' => 'DH-SHA1',
|
|
}
|
|
if @assoc_manager.instance_variable_get(:@compatibility_mode)
|
|
server_resp['ns'] = OPENID2_NS
|
|
end
|
|
return [sess, Message.from_openid_args(server_resp)]
|
|
end
|
|
|
|
def test_success
|
|
sess, server_resp = setup_dh
|
|
ret = @assoc_manager.send(:extract_association, server_resp, sess)
|
|
assert(!ret.nil?)
|
|
assert_equal(ret.assoc_type, 'HMAC-SHA1')
|
|
assert_equal(ret.secret, SECRET)
|
|
assert_equal(ret.handle, 'handle')
|
|
assert_equal(ret.lifetime, 1000)
|
|
end
|
|
|
|
def test_openid2success
|
|
# Use openid 1 type in endpoint so _setUpDH checks
|
|
# compatibility mode state properly
|
|
@assoc_manager.instance_variable_set('@compatibility_mode', true)
|
|
test_success()
|
|
end
|
|
|
|
def test_bad_dh_values
|
|
sess, server_resp = setup_dh
|
|
server_resp.set_arg(OPENID_NS, 'enc_mac_key', '\x00\x00\x00')
|
|
assert_protocol_error('Malformed response for') {
|
|
@assoc_manager.send(:extract_association, server_resp, sess)
|
|
}
|
|
end
|
|
end
|
|
|
|
class TestAssocManagerGetAssociation < Test::Unit::TestCase
|
|
include FetcherMixin
|
|
include TestUtil
|
|
|
|
attr_reader :negotiate_association
|
|
|
|
def setup
|
|
@server_url = 'http://invalid/'
|
|
@store = Store::Memory.new
|
|
@assoc_manager = Consumer::AssociationManager.new(@store, @server_url)
|
|
@assoc_manager.extend(Const)
|
|
@assoc = Association.new('handle', 'secret', Time.now, 10000,
|
|
'HMAC-SHA1')
|
|
end
|
|
|
|
def set_negotiate_response(assoc)
|
|
@assoc_manager.const(:negotiate_association, assoc)
|
|
end
|
|
|
|
def test_not_in_store_no_response
|
|
set_negotiate_response(nil)
|
|
assert_equal(nil, @assoc_manager.get_association)
|
|
end
|
|
|
|
def test_not_in_store_negotiate_assoc
|
|
# Not stored beforehand:
|
|
stored_assoc = @store.get_association(@server_url, @assoc.handle)
|
|
assert_equal(nil, stored_assoc)
|
|
|
|
# Returned from associate call:
|
|
set_negotiate_response(@assoc)
|
|
assert_equal(@assoc, @assoc_manager.get_association)
|
|
|
|
# It should have been stored:
|
|
stored_assoc = @store.get_association(@server_url, @assoc.handle)
|
|
assert_equal(@assoc, stored_assoc)
|
|
end
|
|
|
|
def test_in_store_no_response
|
|
set_negotiate_response(nil)
|
|
@store.store_association(@server_url, @assoc)
|
|
assert_equal(@assoc, @assoc_manager.get_association)
|
|
end
|
|
|
|
def test_request_assoc_with_status_error
|
|
fetcher_class = Class.new do
|
|
define_method(:fetch) do |*args|
|
|
MockResponse.new(500, '')
|
|
end
|
|
end
|
|
with_fetcher(fetcher_class.new) do
|
|
assert_log_matches('Got HTTP status error when requesting') {
|
|
result = @assoc_manager.send(:request_association, 'HMAC-SHA1',
|
|
'no-encryption')
|
|
assert(result.nil?)
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
class TestAssocManagerRequestAssociation < Test::Unit::TestCase
|
|
include FetcherMixin
|
|
include TestUtil
|
|
|
|
def setup
|
|
@assoc_manager = Consumer::AssociationManager.new(nil, 'http://invalid/')
|
|
@assoc_type = 'HMAC-SHA1'
|
|
@session_type = 'no-encryption'
|
|
@message = Message.new(OPENID2_NS)
|
|
@message.update_args(OPENID_NS, {
|
|
'assoc_type' => @assoc_type,
|
|
'session_type' => @session_type,
|
|
'assoc_handle' => 'kaboodle',
|
|
'expires_in' => '1000',
|
|
'mac_key' => 'X' * 20,
|
|
})
|
|
end
|
|
|
|
def make_request
|
|
kv = @message.to_kvform
|
|
fetcher_class = Class.new do
|
|
define_method(:fetch) do |*args|
|
|
MockResponse.new(200, kv)
|
|
end
|
|
end
|
|
with_fetcher(fetcher_class.new) do
|
|
@assoc_manager.send(:request_association, @assoc_type, @session_type)
|
|
end
|
|
end
|
|
|
|
# The association we get is from valid processing of our result,
|
|
# and that no errors are raised
|
|
def test_success
|
|
assert_equal('kaboodle', make_request.handle)
|
|
end
|
|
|
|
# A missing parameter gets translated into a log message and
|
|
# causes the method to return nil
|
|
def test_missing_fields
|
|
@message.del_arg(OPENID_NS, 'assoc_type')
|
|
assert_log_matches('Missing required par') {
|
|
assert_equal(nil, make_request)
|
|
}
|
|
end
|
|
|
|
# A bad value results in a log message and causes the method to
|
|
# return nil
|
|
def test_protocol_error
|
|
@message.set_arg(OPENID_NS, 'expires_in', 'goats')
|
|
assert_log_matches('Protocol error processing') {
|
|
assert_equal(nil, make_request)
|
|
}
|
|
end
|
|
end
|
|
|
|
end
|