@ -230,6 +230,10 @@ class SSOAuthenticator(Authenticator):
# cookies.
_testing_load_expired_cookies = False
# How long we should wait for the sso helper to write and close stdout.
# Overridden in tests.
_timeout_secs = 5
# Tri-state cache for sso helper command:
# * None - no lookup yet
# * () - lookup was performed, but no binary was found.
@ -322,60 +326,80 @@ class SSOAuthenticator(Authenticator):
Raises an exception if something goes wrong .
"""
tdir = tempfile . mkdtemp ( suffix = ' gerrit_util ' )
tf = os . path . join ( tdir , ' git-remote-sso.stderr ' )
cmd = cls . _resolve_sso_cmd ( )
with tempdir ( ) as tdir :
cmd = cls . _resolve_sso_cmd ( )
stderr_file = open ( tf , mode = ' w ' )
# NOTE: The git-remote-sso helper does the following:
#
# 1. writes files to disk.
# 2. writes config to stdout, referencing those files.
# 3. closes stdout (thus sending EOF to us, allowing
# sys.stdout.read() to complete).
# 4. waits for stdin to close.
# 5. deletes files on disk (which is why we make sys.stdin a PIPE
# instead of closing it outright).
#
# NOTE: the http.proxy value in the emitted config points to
# a socket which is owned by a system service, not `proc` itself.
with subprocess2 . Popen ( cmd ,
stdout = subprocess2 . PIPE ,
stderr = stderr_file ,
stdin = subprocess2 . PIPE ,
encoding = ' utf-8 ' ) as proc :
timedout = False
def _fire_timeout ( ) :
nonlocal timedout
timedout = True
proc . kill ( )
timer = threading . Timer ( 5 , _fire_timeout )
timer . start ( )
try :
ret = cls . _parse_config ( proc . stdout . read ( ) )
finally :
timer . cancel ( )
if timedout :
LOGGER . error (
' SSOAuthenticator: Timeout: %r : reading config. ' , cmd )
raise subprocess . TimeoutExpired ( cmd = cmd , timeout = 5 )
proc . poll ( )
if ( retcode := proc . returncode ) is not None :
# process failed - we should be able to read the tempfile.
stderr_file . close ( )
with open ( tf , encoding = ' utf-8 ' ) as stderr :
sys . exit (
f ' SSOAuthenticator: exit { retcode } : { stderr . read ( ) . strip ( ) } '
)
return ret
tf = os . path . join ( tdir , ' git-remote-sso.stderr ' )
with open ( tf , mode = ' w ' ) as stderr_file :
# NOTE: The git-remote-sso helper does the following:
#
# 1. writes files to disk.
# 2. writes config to stdout, referencing those files.
# 3. closes stdout (thus sending EOF to us, allowing
# sys.stdout.read() to complete).
# 4. waits for stdin to close.
# 5. deletes files on disk (which is why we make sys.stdin a PIPE
# instead of closing it outright).
#
# NOTE: the http.proxy value in the emitted config points to
# a socket which is owned by a system service, not `proc` itself.
with subprocess2 . Popen ( cmd ,
stdout = subprocess2 . PIPE ,
stderr = stderr_file ,
stdin = subprocess2 . PIPE ,
encoding = ' utf-8 ' ) as proc :
stderr_file . close ( ) # we can close after process starts.
timedout = False
def _fire_timeout ( ) :
nonlocal timedout
timedout = True
proc . kill ( )
timer = threading . Timer ( cls . _timeout_secs , _fire_timeout )
timer . start ( )
try :
stdout_data = proc . stdout . read ( )
finally :
timer . cancel ( )
if timedout :
LOGGER . error (
' SSOAuthenticator: Timeout: %r : reading config. ' ,
cmd )
raise subprocess . TimeoutExpired (
cmd = cmd , timeout = cls . _timeout_secs )
# if the process already ended, then something is wrong.
retcode = proc . poll ( )
# if stdout was closed without any data, we need to wait for
# end-of-process here and hope for an error message - the
# poll above is racy in this case (we could see stdout EOF
# but the process may not have quit yet).
if not retcode and not stdout_data :
retcode = proc . wait ( timeout = cls . _timeout_secs )
# We timed out while doing `wait` - we can't safely open
# stderr on windows, so just emit a generic timeout
# exception.
if retcode is None :
LOGGER . error (
' SSOAuthenticator: Timeout: %r : waiting error output. ' ,
cmd )
raise subprocess . TimeoutExpired (
cmd = cmd , timeout = cls . _timeout_secs )
# Finally, if the poll or wait ended up getting the retcode,
# it means the process failed, so we can read the stderr
# file and reflect it back to the user.
if retcode is not None :
# process failed - we should be able to read the tempfile.
with open ( tf , encoding = ' utf-8 ' ) as stderr :
sys . exit (
f ' SSOAuthenticator: exit { retcode } : { stderr . read ( ) . strip ( ) } '
)
return cls . _parse_config ( stdout_data )
@classmethod
def _get_sso_info ( cls ) - > SSOInfo :