diff --git a/README.md b/README.md index d1ef178..0fee481 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,17 @@ [![Support](https://img.shields.io/badge/Support-Official-green.svg)](mailto:support@perforce.com) -# p4ruby +# P4Ruby P4Ruby is a wrapper for the P4 C++ API in Ruby. + +P4Ruby is a Ruby module that provides an object-oriented API to Helix Core server. Using P4Ruby is faster than using the command-line interface in scripts, because multiple command can be executed on a single connection, and because it returns Helix server responses as Ruby hashes and arrays. + +## Requirements +For P4Ruby requirements see "Compatibility Statements" in [RELNOTES](https://github.com/perforce/p4ruby/blob/master/RELNOTES.txt#L99) + +## Documentation +Official documentation is located on the [Perforce website](https://www.perforce.com/manuals/p4ruby/Content/P4Ruby/Home-p4ruby.html) + +## Support +P4Ruby is officially supported by Perforce. +Pull requests will be managed by Perforce's engineering teams. We will do our best to acknowledge these in a timely manner based on available capacity. +Issues will not be managed on GitHub. All issues should be recorded via [Perforce's standard support process](https://www.perforce.com/support/request-support). diff --git a/RELNOTES.txt b/RELNOTES.txt index 2c573e0..b4233ac 100644 --- a/RELNOTES.txt +++ b/RELNOTES.txt @@ -1,7 +1,7 @@ Release Notes for P4Ruby, Perforce's script API for Ruby - Version 2021.1 + Version 2021.1 Patch 1 Introduction @@ -68,8 +68,9 @@ Building P4Ruby from Source 3. Execute the build commands: - bundle install - bundle exec rake compile --p4api_dir= + bundle install + bundle exec rake compile -- --p4api_dir= \ + --with-ssl-dir= Note: If the --p4api_dir flag is not provided, P4Ruby will attempt to download and extract correct version of Perforce C++ API @@ -167,6 +168,14 @@ Key to symbols used in change notes below. -------------------------------------------------------------------------- +New functionality in 2021.1 Patch 1 + + #2187837 (Job #108007) * ** + Added callback based SSO handler. + +-------------------------------------------------------------------------- +-------------------------------------------------------------------------- + New functionality in 2021.1 #2148269 #2148274 (Job #107105) * ** diff --git a/ext/P4/clientuserruby.cpp b/ext/P4/clientuserruby.cpp index a6b718e..1d3161b 100644 --- a/ext/P4/clientuserruby.cpp +++ b/ext/P4/clientuserruby.cpp @@ -66,6 +66,19 @@ static const int CANCEL = 2; * server, and converts the data to Ruby form for returning to the caller. ******************************************************************************/ +class SSOShim : public ClientSSO { +public: + SSOShim(ClientUserRuby *ui) : ui(ui) {} + virtual ClientSSOStatus Authorize( StrDict &vars, + int maxLength, + StrBuf &result ) + { + return ui->Authorize(vars, maxLength, result); + } +private: + ClientUserRuby *ui; +} ; + ClientUserRuby::ClientUserRuby(SpecMgr *s) { specMgr = s; debug = 0; @@ -78,15 +91,22 @@ ClientUserRuby::ClientUserRuby(SpecMgr *s) { rubyExcept = 0; alive = 1; track = false; - SetSSOHandler( ssoHandler = new P4ClientSSO( s ) ); + SetSSOHandler( new SSOShim( this ) ); + + ssoEnabled = 0; + ssoResultSet = 0; + ssoResult = Qnil; + ssoHandler = Qnil; ID idP4 = rb_intern("P4"); ID idP4OH = rb_intern("OutputHandler"); ID idP4Progress = rb_intern("Progress"); + ID idP4SSO = rb_intern("SSOHandler"); VALUE cP4 = rb_const_get_at(rb_cObject, idP4); cOutputHandler = rb_const_get_at(cP4, idP4OH); cProgress = rb_const_get_at(cP4, idP4Progress ); + cSSOHandler = rb_const_get_at(cP4, idP4SSO); } void ClientUserRuby::Reset() { @@ -721,47 +741,130 @@ void ClientUserRuby::GCMark() { if (mergeResult != Qnil) rb_gc_mark( mergeResult); if (handler != Qnil) rb_gc_mark( handler); if (progress != Qnil) rb_gc_mark( progress ); - rb_gc_mark( cOutputHandler); + if (ssoResult != Qnil) rb_gc_mark( ssoResult ); + if (ssoHandler != Qnil) rb_gc_mark( ssoHandler ); + rb_gc_mark( cOutputHandler ); rb_gc_mark( cProgress ); + rb_gc_mark( cSSOHandler ); results.GCMark(); - ssoHandler->GCMark(); } -// -// SSO handler -// -P4ClientSSO::P4ClientSSO( SpecMgr *s ) -{ - specMgr = s; - resultSet = 0; - ssoEnabled = 0; - result = Qnil; +/* + * Set the Handler object. Double-check that it is either nil or + * an instance of OutputHandler to avoid future problems + */ + +VALUE +ClientUserRuby::SetRubySSOHandler(VALUE h) { + if (P4RDB_CALLS) fprintf(stderr, "[P4] SetSSOHandler()\n"); + + if (Qnil != h && Qfalse == rb_obj_is_kind_of(h, cSSOHandler)) { + rb_raise(eP4, "Handler needs to be an instance of P4::SSOHandler"); + return Qfalse; + } + + ssoHandler = h; + alive = 1; // ensure that we don't drop out after the next call + + return Qtrue; +} + + +// returns true if output should be reported +// false if the output is handled and should be ignored +static VALUE CallMethodSSO(VALUE data) { + VALUE *args = reinterpret_cast(data); + return rb_funcall(args[0], (ID) rb_intern("authorize"), 2, args[1], args[2]); } ClientSSOStatus -P4ClientSSO::Authorize( StrDict &vars, int maxLength, StrBuf &strbuf ) +ClientUserRuby::CallSSOMethod(VALUE vars, int maxLength, StrBuf &result) { + ClientSSOStatus answer = CSS_SKIP; + int excepted = 0; + result.Clear(); + + if (P4RDB_COMMANDS) fprintf(stderr, "[P4] CallSSOMethod\n"); + + // some wild hacks to satisfy the rb_protect method + + VALUE args[3] = { ssoHandler, vars, INT2NUM( maxLength ) }; + + VALUE res = rb_protect(CallMethodSSO, (VALUE) args, &excepted); + + if (excepted) { // exception thrown + alive = 0; + rb_jump_tag(excepted); + } else if( FIXNUM_P( res ) ) { + int a = NUM2INT(res); + if (P4RDB_COMMANDS) + fprintf(stderr, "[P4] CallSSOMethod returned %d\n", a); + + if( a < CSS_PASS || a > CSS_SKIP ) + rb_raise(eP4, "P4::SSOHandler::authorize returned out of range response"); + answer = (ClientSSOStatus) a; + } else if( Qtrue == rb_obj_is_kind_of(res, rb_cArray) ) { + VALUE resval1 = rb_ary_shift(res); + Check_Type( resval1, T_FIXNUM ); + int a = NUM2INT(resval1); + if( a < CSS_PASS || a > CSS_SKIP ) + rb_raise(eP4, "P4::SSOHandler::authorize returned out of range response"); + answer = (ClientSSOStatus) a; + + VALUE resval2 = rb_ary_shift(res); + if( resval2 != Qnil ) + { + Check_Type( resval2, T_STRING ); + result.Set(StringValuePtr(resval2)); + if (P4RDB_COMMANDS) + fprintf(stderr, "[P4] CallSSOMethod returned %d, %s\n", a, result.Text()); + } + + } else { + Check_Type( res, T_STRING ); + answer = CSS_PASS; + + result.Set(StringValuePtr(res)); + if (P4RDB_COMMANDS) + fprintf(stderr, "[P4] CallSSOMethod returned %s\n", result.Text()); + + } + + return answer; +} + +ClientSSOStatus +ClientUserRuby::Authorize( StrDict &vars, int maxLength, StrBuf &strbuf ) { - ssoVars.Clear(); + ssoVars.Clear(); + + if( ssoHandler != Qnil ) + { + ClientSSOStatus res = CallSSOMethod( specMgr->StrDictToHash(&vars), maxLength, strbuf ); + if( res != CSS_SKIP ) + return res; + if (P4RDB_COMMANDS) + fprintf(stderr, "[P4] Authorize skipped result from SSO Handler\n" ); + } - if( !ssoEnabled ) - return CSS_SKIP; + if( !ssoEnabled ) + return CSS_SKIP; - if( ssoEnabled < 0 ) - return CSS_UNSET; + if( ssoEnabled < 0 ) + return CSS_UNSET; - if( resultSet ) - { - strbuf.Clear(); - VALUE resval = result; + if( ssoResultSet ) + { + strbuf.Clear(); + VALUE resval = ssoResult; - //if( P4RDB_CALLS ) - // std::cerr << "[P4] ClientSSO::Authorize(). Using supplied input" - // << std::endl; + //if( P4RDB_CALLS ) + // std::cerr << "[P4] ClientSSO::Authorize(). Using supplied input" + // << std::endl; - if (Qtrue == rb_obj_is_kind_of(result, rb_cArray)) { - resval = rb_ary_shift(result); + if (Qtrue == rb_obj_is_kind_of(ssoResult, rb_cArray)) { + resval = rb_ary_shift(ssoResult); } if( resval != Qnil ) { @@ -771,111 +874,106 @@ P4ClientSSO::Authorize( StrDict &vars, int maxLength, StrBuf &strbuf ) strbuf.Set(StringValuePtr(str)); } - return resultSet == 2 ? CSS_FAIL - : CSS_PASS; - } + return ssoResultSet == 2 ? CSS_FAIL + : CSS_PASS; + } - ssoVars.CopyVars( vars ); - return CSS_EXIT; + ssoVars.CopyVars( vars ); + return CSS_EXIT; } VALUE -P4ClientSSO::EnableSSO( VALUE e ) +ClientUserRuby::EnableSSO( VALUE e ) { - if( e == Qnil ) - { - ssoEnabled = 0; - return Qtrue; - } - - if( e == Qtrue ) - { - ssoEnabled = 1; - return Qtrue; - } - - if( e == Qfalse ) - { - ssoEnabled = -1; - return Qtrue; - } + if( e == Qnil ) + { + ssoEnabled = 0; + return Qtrue; + } + + if( e == Qtrue ) + { + ssoEnabled = 1; + return Qtrue; + } + + if( e == Qfalse ) + { + ssoEnabled = -1; + return Qtrue; + } return Qfalse; } VALUE -P4ClientSSO::SSOEnabled() +ClientUserRuby::SSOEnabled() { - if( ssoEnabled == 1 ) - { - return Qtrue; - } - else if( ssoEnabled == -1 ) - { - return Qfalse; - } - else - { - return Qnil; - } + if( ssoEnabled == 1 ) + { + return Qtrue; + } + else if( ssoEnabled == -1 ) + { + return Qfalse; + } + else + { + return Qnil; + } } VALUE -P4ClientSSO::SetPassResult( VALUE i ) +ClientUserRuby::SetSSOPassResult( VALUE i ) { - resultSet = 1; - return SetResult( i ); + ssoResultSet = 1; + return SetSSOResult( i ); } VALUE -P4ClientSSO::GetPassResult() +ClientUserRuby::GetSSOPassResult() { - if( resultSet == 1 ) - { - return result; - } - else - { - return Qnil; - } + if( ssoResultSet == 1 ) + { + return ssoResult; + } + else + { + return Qnil; + } } VALUE -P4ClientSSO::SetFailResult( VALUE i ) +ClientUserRuby::SetSSOFailResult( VALUE i ) { - resultSet = 2; - return SetResult( i ); + ssoResultSet = 2; + return SetSSOResult( i ); } VALUE -P4ClientSSO::GetFailResult() +ClientUserRuby::GetSSOFailResult() { - if( resultSet == 2 ) - { - return result; - } - else - { - return Qnil; - } + if( ssoResultSet == 2 ) + { + return ssoResult; + } + else + { + return Qnil; + } } VALUE -P4ClientSSO::SetResult( VALUE i ) +ClientUserRuby::SetSSOResult( VALUE i ) { - //if (P4RDB_CALLS) fprintf(stderr, "[P4] P4ClientSSO::SetResult()\n"); + if (P4RDB_CALLS) fprintf(stderr, "[P4] P4ClientSSO::SetResult()\n"); - result = i; + ssoResult = i; return Qtrue; } VALUE -P4ClientSSO::GetSSOVars() +ClientUserRuby::GetSSOVars() { - return specMgr->StrDictToHash( &ssoVars ); -} - -void -P4ClientSSO::GCMark() { - if (result != Qnil) rb_gc_mark( result ); + return specMgr->StrDictToHash( &ssoVars ); } \ No newline at end of file diff --git a/ext/P4/clientuserruby.h b/ext/P4/clientuserruby.h index 8ebb6c6..6cca96d 100644 --- a/ext/P4/clientuserruby.h +++ b/ext/P4/clientuserruby.h @@ -42,40 +42,7 @@ class SpecMgr; class ClientProgress; -class P4ClientSSO : public ClientSSO -{ - public: - P4ClientSSO( SpecMgr *s ); - - // Client SSO methods overridden here - virtual ClientSSOStatus Authorize( StrDict &vars, int maxLength, - StrBuf &result ); - - // Local methods - VALUE EnableSSO( VALUE e ); - VALUE SSOEnabled(); - VALUE SetPassResult( VALUE i ); - VALUE GetPassResult(); - VALUE SetFailResult( VALUE i ); - VALUE GetFailResult(); - VALUE GetSSOVars(); - - void GCMark(); - - private: - - VALUE SetResult( VALUE i ); - - int ssoEnabled; - int resultSet; - - StrBufDict ssoVars; - SpecMgr * specMgr; - - VALUE result; -}; - -class ClientUserRuby: public ClientUser, public KeepAlive { +class ClientUserRuby: public ClientUser, public ClientSSO, public KeepAlive { public: ClientUserRuby(SpecMgr *s); @@ -136,13 +103,18 @@ class ClientUserRuby: public ClientUser, public KeepAlive { // SSO handler support - VALUE EnableSSO( VALUE e ) { return ssoHandler->EnableSSO( e ); } - VALUE SSOEnabled() { return ssoHandler->SSOEnabled(); } - VALUE SetSSOPassResult( VALUE i ) { return ssoHandler->SetPassResult( i ); } - VALUE GetSSOPassResult(){ return ssoHandler->GetPassResult();} - VALUE SetSSOFailResult( VALUE i ) { return ssoHandler->SetFailResult( i ); } - VALUE GetSSOFailResult(){ return ssoHandler->GetFailResult();} - VALUE GetSSOVars() { return ssoHandler->GetSSOVars(); } + virtual ClientSSOStatus Authorize( StrDict &vars, int maxLength, StrBuf &result ); + VALUE EnableSSO( VALUE e ); + VALUE SSOEnabled(); + VALUE SetSSOPassResult( VALUE i ); + VALUE GetSSOPassResult(); + VALUE SetSSOFailResult( VALUE i ); + VALUE GetSSOFailResult(); + VALUE GetSSOVars(); + VALUE SetRubySSOHandler( VALUE handler ); + VALUE GetRubySSOHandler() { + return ssoHandler; + } // override from KeepAlive virtual int IsAlive() { @@ -155,6 +127,8 @@ class ClientUserRuby: public ClientUser, public KeepAlive { void ProcessOutput(const char * method, VALUE data); void ProcessMessage(Error * e); bool CallOutputMethod(const char * method, VALUE data); + VALUE SetSSOResult( VALUE i ); + ClientSSOStatus CallSSOMethod(VALUE vars, int maxLength, StrBuf &result); private: StrBuf cmd; @@ -167,11 +141,18 @@ class ClientUserRuby: public ClientUser, public KeepAlive { VALUE cOutputHandler; VALUE progress; VALUE cProgress; + VALUE cSSOHandler; int debug; int apiLevel; int alive; int rubyExcept; bool track; - P4ClientSSO * ssoHandler; + + // SSO handler support + int ssoEnabled; + int ssoResultSet; + StrBufDict ssoVars; + VALUE ssoResult; + VALUE ssoHandler; }; diff --git a/ext/P4/extconf.rb b/ext/P4/extconf.rb index f4519f7..1ed8633 100644 --- a/ext/P4/extconf.rb +++ b/ext/P4/extconf.rb @@ -444,15 +444,11 @@ def p4_cpu(os) # directory name where we can download files from. def p4_platform_label case RbConfig::CONFIG["target_os"].downcase - when /nt|mswin|mingw/ + when /nt|mswin|mingw|cygwin|msys/ # Ruby on windows is only MinGW via Rubyinstaller.org, though this may # not work on all rubies. - if RbConfig::CONFIG['MAJOR'].to_i >= 2 - # Note that x64 or x86 needs to be suffixed to this - 'mingw64' - else - 'mingwx86' - end + # There are too many permutations of Windows p4api, to automate. + raise 'Automatic fetching of p4api from perforce FTP is not supported on Windows' when /darwin19|darwin[2-9][0-9]/ "macosx1015#{p4_cpu(:darwin)}" when /darwin/ @@ -460,9 +456,7 @@ def p4_platform_label when /solaris/ "solaris10#{p4_cpu(:solaris)}" when /linux/ - "linux26#{p4_cpu(:linux)}" - when /cygwin/ - raise 'cygwin is not supported for the --download-p4api option' + "linux26#{p4_cpu(:linux)}" end end diff --git a/ext/P4/p4.cpp b/ext/P4/p4.cpp index 50806af..516a149 100644 --- a/ext/P4/p4.cpp +++ b/ext/P4/p4.cpp @@ -878,6 +878,20 @@ static VALUE p4_set_sso_failresult( VALUE self, VALUE result ) Data_Get_Struct( self, P4ClientApi, p4 ); return p4->SetSSOFailResult( result ); } +static VALUE p4_get_ssohandler( VALUE self ) +{ + P4ClientApi *p4; + Data_Get_Struct( self, P4ClientApi, p4 ); + return p4->GetSSOHandler(); +} + +static VALUE p4_set_ssohandler( VALUE self, VALUE handler ) +{ + P4ClientApi *p4; + Data_Get_Struct( self, P4ClientApi, p4 ); + p4->SetSSOHandler( handler ); + return Qtrue; +} /******************************************************************************* * P4::MergeData methods. Construction/destruction defined elsewhere @@ -1416,6 +1430,8 @@ void Init_P4() rb_define_method( cP4, "ssopassresult=", RUBY_METHOD_FUNC(p4_set_sso_passresult), 1); rb_define_method( cP4, "ssofailresult", RUBY_METHOD_FUNC(p4_get_sso_failresult), 0); rb_define_method( cP4, "ssofailresult=", RUBY_METHOD_FUNC(p4_set_sso_failresult), 1); + rb_define_method( cP4, "ssohandler", RUBY_METHOD_FUNC(p4_get_ssohandler), 0); + rb_define_method( cP4, "ssohandler=", RUBY_METHOD_FUNC(p4_set_ssohandler), 1); // P4::MergeData class diff --git a/ext/P4/p4clientapi.cpp b/ext/P4/p4clientapi.cpp index 4af0a8d..a8d891d 100644 --- a/ext/P4/p4clientapi.cpp +++ b/ext/P4/p4clientapi.cpp @@ -715,6 +715,17 @@ P4ClientApi::SetProgress( VALUE progress ) { return ui.SetProgress( progress ); } +VALUE +P4ClientApi::SetSSOHandler( VALUE h ) +{ + if ( P4RDB_COMMANDS ) + fprintf( stderr, "[P4] Received SSO handler object\n" ); + + ui.SetRubySSOHandler( h ); + + return Qtrue; +} + void P4ClientApi::GCMark() diff --git a/ext/P4/p4clientapi.h b/ext/P4/p4clientapi.h index 7090b29..4db61e9 100644 --- a/ext/P4/p4clientapi.h +++ b/ext/P4/p4clientapi.h @@ -179,6 +179,8 @@ class P4ClientApi VALUE GetSSOPassResult(); VALUE SetSSOFailResult( VALUE r ); VALUE GetSSOFailResult(); + VALUE SetSSOHandler( VALUE handler ); + VALUE GetSSOHandler() { return ui.GetRubySSOHandler(); } // Ruby garbage collection void GCMark(); diff --git a/lib/P4.rb b/lib/P4.rb index b1e1e69..6761ffa 100644 --- a/lib/P4.rb +++ b/lib/P4.rb @@ -118,6 +118,13 @@ class P4 PROG_FAILDONE = 2 PROG_FLUSH = 3 + # SSO Handler return values constants + SSO_PASS = 0 # SSO succeeded (result is an authentication token) + SSO_FAIL = 1 # SSO failed (result will be logged as error message) + SSO_UNSET = 2 # Client has no SSO support + SSO_EXIT = 3 # Stop login process + SSO_SKIP = 4 # Fall back to default P4API behavior + # Mappings for P4#each_ # Hash of type vs. key SpecTypes = { @@ -669,4 +676,13 @@ def outputMessage(message) HANDLED end end + + #***************************************************************************** + # P4::SSOHandler class. + #***************************************************************************** + class SSOHandler + def authorize(vars, maxLength) + [ SSO_SKIP, "" ] + end + end end # class P4 diff --git a/test/25_sso_handler_test.rb b/test/25_sso_handler_test.rb index 5eebf42..95c26e7 100644 --- a/test/25_sso_handler_test.rb +++ b/test/25_sso_handler_test.rb @@ -121,7 +121,7 @@ def test_enabled end assert_equal( {}, p4.ssovars ) rescue P4Exception => e - assert( false, "Exception thrown: #{e}" ) + assert( false, "Exception thrown: #{e}" ) ensure p4.disconnect end @@ -208,4 +208,222 @@ def test_pass_result end end end + + # Callback tests + + class MySSOHandler < P4::SSOHandler + def initialize( resp ) + @result = resp + @vars = nil + @maxlen = nil + end + + attr_reader :vars, :maxlen + + def authorize(vars, maxLength) + @vars = vars + @maxlen = maxLength + return @result + end + end + + + def test_callback_in_out + begin + h = MySSOHandler.new([P4::SSO_PASS, "Handler good result!"]) + assert( h.kind_of?( MySSOHandler ), "Failed to create MySSOHandler" ) + assert( h.kind_of?( P4::SSOHandler ), "MySSOHandler is not a SSOHandler" ) + assert( h.authorize({"foo" => "bar"}, 128) == [P4::SSO_PASS, "Handler good result!"], "MySSOHandler repsonse is good" ) + + assert( h.vars.kind_of?( Hash ) ) + assert_equal( ["foo"], h.vars.keys ) + assert_equal( 128, h.maxlen ) + end + end + + def test_callback_pass_1 + begin + assert( p4, "Failed to create Perforce client" ) + + h = MySSOHandler.new([P4::SSO_PASS, "Handler good result!"]) + assert( h.kind_of?( MySSOHandler ), "Failed to create MySSOHandler" ) + assert( h.kind_of?( P4::SSOHandler ), "MySSOHandler is not a SSOHandler" ) + + assert( h.authorize(Hash.new, 128) == [P4::SSO_PASS, "Handler good result!"], "MySSOHandler repsonse is good" ) + + # verify that we get a "you need to accept" when connecting + begin + p4.connect + p4.ssohandler = h + assert( p4.ssohandler == h, "Failed to set/get MySSOHandler" ) + + begin + assert_equal( ["User", "TicketExpiration"], p4.run( 'login' )[0].keys ) + rescue P4Exception => e + assert( e.to_s.include?("[Error]: Single sign-on on client failed: My bad result!"), "Exception thrown: #{e}" ) + end + assert( h.vars.kind_of?( Hash ) ) + assert_equal( ["user", "serverAddress", "P4PORT", "ssoArgs", "data"], h.vars.keys ) + assert_equal( 131072, h.maxlen ) + rescue P4Exception => e + assert( false, "Exception thrown: #{e}" ) + ensure + p4.disconnect + end + end + end + + def test_callback_pass_2 + begin + assert( p4, "Failed to create Perforce client" ) + + h = MySSOHandler.new(P4::SSO_PASS) + assert( h.kind_of?( MySSOHandler ), "Failed to create MySSOHandler" ) + assert( h.kind_of?( P4::SSOHandler ), "MySSOHandler is not a SSOHandler" ) + + assert( h.authorize(Hash.new, 128) == P4::SSO_PASS, "MySSOHandler repsonse is good" ) + + # verify that we get a "you need to accept" when connecting + begin + p4.connect + p4.ssohandler = h + assert( p4.ssohandler == h, "Failed to set/get MySSOHandler" ) + + begin + assert_equal( ["User", "TicketExpiration"], p4.run( 'login' )[0].keys ) + rescue P4Exception => e + assert( e.to_s.include?("[Error]: Single sign-on on client failed: My bad result!"), "Exception thrown: #{e}" ) + end + assert( h.vars.kind_of?( Hash ) ) + assert_equal( ["user", "serverAddress", "P4PORT", "ssoArgs", "data"], h.vars.keys ) + assert_equal( 131072, h.maxlen ) + rescue P4Exception => e + assert( false, "Exception thrown: #{e}" ) + ensure + p4.disconnect + end + end + end + + def test_callback_pass_3 + begin + assert( p4, "Failed to create Perforce client" ) + + h = MySSOHandler.new("Handler good result!") + assert( h.kind_of?( MySSOHandler ), "Failed to create MySSOHandler" ) + assert( h.kind_of?( P4::SSOHandler ), "MySSOHandler is not a SSOHandler" ) + + assert( h.authorize(Hash.new, 128) == "Handler good result!", "MySSOHandler repsonse is good" ) + + # verify that we get a "you need to accept" when connecting + begin + p4.connect + p4.ssohandler = h + assert( p4.ssohandler == h, "Failed to set/get MySSOHandler" ) + + begin + assert_equal( ["User", "TicketExpiration"], p4.run( 'login' )[0].keys ) + rescue P4Exception => e + assert( e.to_s.include?("[Error]: Single sign-on on client failed: My bad result!"), "Exception thrown: #{e}" ) + end + assert( h.vars.kind_of?( Hash ) ) + assert_equal( ["user", "serverAddress", "P4PORT", "ssoArgs", "data"], h.vars.keys ) + assert_equal( 131072, h.maxlen ) + rescue P4Exception => e + assert( false, "Exception thrown: #{e}" ) + ensure + p4.disconnect + end + end + end + + def test_callback_fail + begin + assert( p4, "Failed to create Perforce client" ) + + h = MySSOHandler.new([P4::SSO_FAIL, "Handler bad result!"]) + assert( h.kind_of?( MySSOHandler ), "Failed to create MySSOHandler" ) + assert( h.kind_of?( P4::SSOHandler ), "MySSOHandler is not a SSOHandler" ) + + assert( h.authorize(Hash.new, 128) == [P4::SSO_FAIL, "Handler bad result!"], "MySSOHandler repsonse is good" ) + + # verify that we get a "you need to accept" when connecting + begin + p4.connect + p4.ssohandler = h + assert( p4.ssohandler == h, "Failed to set/get MySSOHandler" ) + + begin + assert_equal( ["User", "TicketExpiration"], p4.run( 'login' )[0].keys ) + rescue P4Exception => e + assert( e.to_s.include?("[Error]: Single sign-on on client failed: Handler bad result!"), "Exception thrown: #{e}" ) + end + assert( h.vars.kind_of?( Hash ) ) + assert_equal( ["user", "serverAddress", "P4PORT", "ssoArgs", "data"], h.vars.keys ) + assert_equal( 131072, h.maxlen ) + rescue P4Exception => e + assert( false, "Exception thrown: #{e}" ) + ensure + p4.disconnect + end + end + end + + def test_callback_illegal_1 + begin + assert( p4, "Failed to create Perforce client" ) + + h = MySSOHandler.new(["Handler bad illegal!", P4::SSO_FAIL]) + assert( h.kind_of?( MySSOHandler ), "Failed to create MySSOHandler" ) + assert( h.kind_of?( P4::SSOHandler ), "MySSOHandler is not a SSOHandler" ) + + assert( h.authorize(Hash.new, 128) == ["Handler bad illegal!", P4::SSO_FAIL], "MySSOHandler repsonse is good" ) + + # verify that we get a "you need to accept" when connecting + begin + p4.connect + p4.ssohandler = h + assert( p4.ssohandler == h, "Failed to set/get MySSOHandler" ) + + begin + assert_equal( ["User", "TicketExpiration"], p4.run( 'login' )[0].keys ) + rescue Exception => e + assert( e.to_s.match?("wrong argument type String \\(expected (Fixnum|Integer)\\)"), "Exception thrown: #{e}" ) + end + rescue P4Exception => e + assert( false, "Exception thrown: #{e}" ) + ensure + p4.disconnect + end + end + end + + def test_callback_illegal_2 + begin + assert( p4, "Failed to create Perforce client" ) + + h = MySSOHandler.new([15, "Handler bad illegal!"]) + assert( h.kind_of?( MySSOHandler ), "Failed to create MySSOHandler" ) + assert( h.kind_of?( P4::SSOHandler ), "MySSOHandler is not a SSOHandler" ) + + assert( h.authorize(Hash.new, 128) == [15, "Handler bad illegal!"], "MySSOHandler repsonse is good" ) + + # verify that we get a "you need to accept" when connecting + begin + p4.connect + p4.ssohandler = h + assert( p4.ssohandler == h, "Failed to set/get MySSOHandler" ) + + begin + assert_equal( ["User", "TicketExpiration"], p4.run( 'login' )[0].keys ) + rescue Exception => e + assert( e.to_s.include?("P4::SSOHandler::authorize returned out of range response"), "Exception thrown: #{e}" ) + end + rescue P4Exception => e + assert( false, "Exception thrown: #{e}" ) + ensure + p4.disconnect + end + end + end end