require File.dirname(__FILE__) + '/../test_helper' require 'openid_controller' require 'crypto_methods' # Re-raise errors caught by the controller. class OpenidController; def rescue_action(e) raise e end; end class OpenidControllerTest < Test::Unit::TestCase fixtures :assocs def setup @controller = OpenidController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @trust_root = "http://#{@request.host}/" end def test_invalid_mode post :server, :controller=>'openid', 'openid.mode'=>'invalid' assert_response 400 assert_tag :tag=>'p', :content => 'openid.mode is missing or invalid' end ###################################################################### # server.associate # ###################################################################### def test_associate_cleartext post :server, :controller=>'openid', 'openid.mode'=>'associate' assert_response :success parse_response assert_match /^([A-Za-z0-9+\/]+=*)$/, @response.mac_key assert_match /^\{HMAC-SHA1\}\d+\/\d+$/, @response.assoc_handle assert_equal 7200, @response.expires_in.to_i assert_equal 'HMAC-SHA1', @response.assoc_type assoc = Assoc.find :first, :conditions=>['handle=?',@response.assoc_handle] assert_equal assoc.secret, @response.mac_key.unbase64 end def test_associate_dh_sha1 dh = DiffieHellman.new post :server, :controller=>'openid', 'openid.mode'=>'associate', 'openid.session_type'=>'DH-SHA1', 'openid.dh_consumer_public'=>dh.createKeyExchange.btwoc.base64 assert_response :success parse_response assert_match /^([A-Za-z0-9+\/]+=*)$/, @response.enc_mac_key assert_match /^([A-Za-z0-9+\/]+=*)$/, @response.dh_server_public assert_match /^\{HMAC-SHA1\}\d+\/\d+$/, @response.assoc_handle assert_equal 7200, @response.expires_in.to_i assert_equal 'HMAC-SHA1', @response.assoc_type dh_shared = dh.decryptKeyExchange(@response.dh_server_public.unbase64.unbtwoc) assoc = Assoc.find :first, :conditions=>['handle=?',@response.assoc_handle] assert_equal assoc.secret, @response.enc_mac_key.unbase64 ^ dh_shared.btwoc.sha1 end def test_associate_missing_dh_consumer_public post :server, :controller=>'openid', 'openid.mode'=>'associate', 'openid.session_type'=>'DH-SHA1' assert_response 400 assert_tag :tag=>'p', :content => 'openid.dh_consumer_public is missing or invalid' end def test_associate_invalid_session_type post :server, :controller=>'openid', 'openid.mode'=>'associate', 'openid.session_type'=>'invalid' assert_response 400 assert_tag :tag=>'p', :content => 'openid.session_type is missing or invalid' end ###################################################################### # server.checkid_setup # ###################################################################### def test_checkid_setup_not_logged_on post :server, :controller=>'openid', 'openid.mode'=>'checkid_setup', 'openid.return_to'=>url_for(:consumer), 'openid.trust_root'=>@trust_root, 'openid.identity'=>url_for(:identity, :user=>'rubys') assert_redirected_to :action=>'decide' end def test_checkid_setup_identity_missing @request.cookies['user'] = CGI::Cookie.new('user', 'rubys') post :server, :controller=>'openid', 'openid.mode'=>'checkid_setup' assert_response 400 assert_tag :tag=>'p', :content => 'openid.identity is missing or invalid' end def test_checkid_setup_identity_invalid @request.cookies['user'] = CGI::Cookie.new('user', 'rubys') post :server, :controller=>'openid', 'openid.mode'=>'checkid_setup', 'openid.return_to'=>url_for(:consumer), 'openid.trust_root'=>@trust_root, 'openid.identity'=>url_for(:identity, :user=>'somebodyelse') assert_redirected_to :action=>'decide' end def test_checkid_setup_trust_root_missing @request.cookies['user'] = CGI::Cookie.new('user', 'rubys') post :server, :controller=>'openid', 'openid.mode'=>'checkid_setup', 'openid.return_to'=>url_for(:consumer), 'openid.identity'=>url_for(:identity, :user=>'rubys') assert_response 400 assert_tag :tag=>'p', :content => 'openid.trust_root is missing or invalid' end def test_checkid_setup_return_to_missing @request.cookies['user'] = CGI::Cookie.new('user', 'rubys') post :server, :controller=>'openid', 'openid.mode'=>'checkid_setup', 'openid.identity'=>url_for(:identity, :user=>'rubys'), 'openid.trust_root'=>'http://evil.host/', 'openid.return_to'=>'http://evil.host/simple.cgi' assert_redirected_to :action=>'decide', 'openid.identity'=>url_for(:identity, :user=>'rubys') end def test_checkid_setup_return_to_missing @request.cookies['user'] = CGI::Cookie.new('user', 'rubys') post :server, :controller=>'openid', 'openid.mode'=>'checkid_setup', 'openid.identity'=>url_for(:identity, :user=>'rubys'), 'openid.trust_root'=>@trust_root assert_response 400 assert_tag :tag=>'p', :content => 'openid.return_to is missing or invalid' end def test_checkid_setup_assoc_expired @request.cookies['user'] = CGI::Cookie.new('user', 'rubys') post :server, :controller=>'openid', 'openid.mode'=>'checkid_setup', 'openid.identity'=>url_for(:identity, :user=>'rubys'), 'openid.trust_root'=>@trust_root, 'openid.return_to'=>url_for(:consumer), 'openid.assoc_handle'=>@expired_assoc.handle assert_response :redirect parse_response assert_equal url_for(:consumer), @response.redirect_url.split('?')[0] assert_equal @expired_assoc.handle, openid.invalidate_handle end def test_checkid_setup_all_ok @request.cookies['user'] = CGI::Cookie.new('user', 'rubys') post :server, :controller=>'openid', 'openid.mode'=>'checkid_setup', 'openid.identity'=>url_for(:identity, :user=>'rubys'), 'openid.trust_root'=>@trust_root, 'openid.return_to'=>url_for(:consumer), 'openid.assoc_handle'=>@live_assoc.handle assert_response :redirect parse_response assert_equal url_for(:consumer), @response.redirect_url.split('?')[0] assert_equal url_for(:identity, :user=>'rubys'), openid.identity assert_equal @live_assoc.handle, openid.assoc_handle assert_equal url_for(:consumer), openid.return_to assert_equal 'mode,identity,return_to', openid.signed assert_match /^([A-Za-z0-9+\/]+=*)$/, openid.sig assert_equal openid.sig, @response.kv.sign(@live_assoc.secret, openid.signed.split(',')) assert_nil openid.invalidate_handle end ###################################################################### # server.check_authentication # ###################################################################### def test_check_authentication_missing_assoc_handle post :server, :controller=>'openid', 'openid.mode'=>'check_authentication' assert_response 400 assert_tag :tag=>'p', :content => 'openid.assoc_handle is missing or invalid' end def test_check_authentication_missing_signed post :server, :controller=>'openid', 'openid.mode'=>'check_authentication', 'openid.assoc_handle'=>@live_assoc.handle assert_response 400 assert_tag :tag=>'p', :content => 'openid.signed is missing or invalid' end def test_check_authentication_missing_sig post :server, :controller=>'openid', 'openid.mode'=>'check_authentication', 'openid.assoc_handle'=>@live_assoc.handle, 'openid.signed'=>'mode,assoc_handle' assert_response 400 assert_tag :tag=>'p', :content => 'openid.sig is missing or invalid' end def test_check_authentication_invalid_sig post :server, :controller=>'openid', 'openid.mode'=>'check_authentication', 'openid.assoc_handle'=>@live_assoc.handle, 'openid.signed'=>'mode,assoc_handle', 'openid.sig'=>'invalid' assert_response :success parse_response assert_equal 'false', @response.is_valid end def test_check_authentication_all_ok reply = { 'openid.mode'=>'id_res', 'openid.assoc_handle'=>@live_assoc.handle, 'openid.signed'=>'mode,assoc_handle' } reply['openid.sig'] = reply.sign(@live_assoc.secret, reply['openid.signed'].split(',')) request = reply.dup request['openid.mode'] = 'check_authentication' post :server, request assert_response :success parse_response assert_equal 'true', @response.is_valid end ###################################################################### # consumer request # ###################################################################### def test_autodiscovery server, delegate = @controller.send :autodiscover, url_for(:identity, :user=>'rubys') assert_equal url_for(:server), server assert_nil delegate end def test_consumer_form get :consumer assert_response :success assert_no_tag :tag=>'div', :attributes => {:id => 'alert'} assert_tag :tag=>'input', :attributes => {:name => 'identity'} end def test_consumer_invalid_post post :consumer assert_response :success assert_tag :tag=>'input', :attributes => {:name => 'identity'} assert_tag :tag=>'div', :attributes => {:id => 'alert'}, :content=>'Please enter an Identity URL' end def test_consumer_post_new post :consumer, :identity=>url_for(:identity, :user=>'rubys') assert_response :redirect parse_response assert_equal url_for(:server), @response.redirect_url.split('?')[0] assert_equal 'checkid_setup', openid.mode assert_equal url_for(:identity, :user=>'rubys'), openid.identity assert_match /^\{HMAC-SHA1\}\d+\/\d+$/, openid.assoc_handle assert_equal url_for(:consumer), openid.return_to assert_equal url_for(:consumer), openid.trust_root end def test_consumer_post_live @live_assoc.identity=url_for(:identity, :user=>'rubys') @live_assoc.save! post :consumer, :identity=>url_for(:identity, :user=>'rubys') assert_response :redirect parse_response assert_equal url_for(:server), @response.redirect_url.split('?')[0] assert_equal 'checkid_setup', openid.mode assert_equal url_for(:identity, :user=>'rubys'), openid.identity assert_equal @live_assoc.handle, openid.assoc_handle assert_equal url_for(:consumer), openid.return_to assert_equal url_for(:consumer), openid.trust_root end def test_consumer_post_expired @expired_assoc.identity=url_for(:identity, :user=>'rubys') @expired_assoc.save! post :consumer, :identity=>url_for(:identity, :user=>'rubys') assert_response :redirect parse_response assert_equal url_for(:server), @response.redirect_url.split('?')[0] assert_equal 'checkid_setup', openid.mode assert_equal url_for(:identity, :user=>'rubys'), openid.identity assert_not_equal @expired_assoc.handle, openid.assoc_handle assert_equal url_for(:consumer), openid.return_to assert_equal url_for(:consumer), openid.trust_root end ###################################################################### # consumer response # ###################################################################### def test_consumer_cancel get :consumer, 'openid.mode'=>'cancel' assert_response :success assert_tag :tag=>'input', :attributes => {:name => 'identity'} assert_tag :tag=>'div', :attributes => {:id => 'alert'}, :content=>'Cancelled by user' end def test_consumer_valid_smart @live_assoc.identity=url_for(:identity, :user=>'rubys') @live_assoc.save! reply = { 'openid.mode'=>'id_res', 'openid.identity'=>url_for(:identity, :user=>'rubys'), 'openid.assoc_handle'=>@live_assoc.handle, 'openid.return_to'=>url_for(:consumer), 'openid.signed'=>'mode,identity,return_to', } reply['openid.sig'] = reply.sign(@live_assoc.secret, reply['openid.signed'].split(',')) get :consumer, reply assert_response :success assert_tag :tag=>'div', :attributes => {:id => 'alert'}, :content=>'Identity verified' end def test_consumer_invalid_smart @live_assoc.identity=url_for(:identity, :user=>'rubys') @live_assoc.save! reply = { 'openid.mode'=>'id_res', 'openid.identity'=>url_for(:identity, :user=>'rubys'), 'openid.assoc_handle'=>@live_assoc.handle, 'openid.return_to'=>url_for(:consumer), 'openid.signed'=>'mode,identity,return_to', } reply['openid.sig'] = reply.sign(@live_assoc.secret, reply['openid.signed'].split(',')) reply['openid.sig'].succ! get :consumer, reply assert_response :success assert_tag :tag=>'div', :attributes => {:id => 'alert'}, :content=>'Signature not valid' end def test_consumer_valid_degrade_to_dumb @expired_assoc.identity=url_for(:identity, :user=>'rubys') @expired_assoc.save! reply = { 'openid.mode'=>'id_res', 'openid.identity'=>url_for(:identity, :user=>'rubys'), 'openid.assoc_handle'=>@live_assoc.handle, 'openid.return_to'=>url_for(:consumer), 'openid.signed'=>'mode,identity,return_to', 'openid.invalidate_handle'=>@expired_assoc.handle } reply['openid.sig'] = reply.sign(@live_assoc.secret, reply['openid.signed'].split(',')) get :consumer, reply assert_response :success assert_tag :tag=>'div', :attributes => {:id => 'alert'}, :content=>'Identity verified' end def test_consumer_valid_degrade_to_dumb @expired_assoc.identity=url_for(:identity, :user=>'rubys') @expired_assoc.save! reply = { 'openid.mode'=>'id_res', 'openid.identity'=>url_for(:identity, :user=>'rubys'), 'openid.assoc_handle'=>@live_assoc.handle, 'openid.return_to'=>url_for(:consumer), 'openid.signed'=>'mode,identity,return_to', 'openid.invalidate_handle'=>@expired_assoc.handle } reply['openid.sig'] = reply.sign(@live_assoc.secret, reply['openid.signed'].split(',')) reply['openid.sig'].succ! get :consumer, reply assert_response :success assert_tag :tag=>'div', :attributes => {:id => 'alert'}, :content=>'Identity NOT verified' end private ###################################################################### # Utility Methods # ###################################################################### def url_for action, parameters={} options = @controller.send(:rewrite_options, parameters) options.update(:action => action, :controller=>@controller.controller_name) ActionController::UrlRewriter.new(@request, parameters).rewrite(options) end def parse_response @response.instance_eval { if response_code == 302 @kv = URI.parse(redirect_url).get_params else @kv = body.parsekv end def kv @kv end def method_missing symbol, *args @kv[symbol.to_s] end } end def openid if ! @openid @openid = @response.kv.dup def @openid.method_missing symbol, *args self['openid.'+symbol.to_s] end end @openid end end ######################################################################## # Mock-ups # ######################################################################## module URI @@testhost = ActionController::TestRequest.new.host instance_eval "alias uriparse parse" def self.parse string uri = uriparse string if uri.host == @@testhost uri.instance_eval { @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @request.request_uri = request_uri def get controller = ActionController::Routing::Routes.recognize!(@request) controller.process(@request,@response) end def post parameters @request.query_parameters.update(parameters) @request.env['REQUEST_METHOD'] = "POST" get end } end return uri end end module ActionController class TestResponse def code headers['Status'].split(' ')[0] end def message headers['Status'].split(' ',2)[1] end end end