diff --git a/README.md b/README.md index 9b0effc3..6e113946 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,9 @@ Note, in the `pam_password` case, this involves sending the cleartext password to the server (SSL should thus be enabled!) and then writing the scrambled token that returns from the transaction. -If an .irodsA file exists already, it will be overwritten. +If an .irodsA file exists already, it will be overwritten by default; however, if these functions' +overwrite parameter is set to `False`, an exception of type `irods.client_init.irodsA_already_exists` +will be raised to indicate the older .irodsA file is present. Examples: For the `native` authentication scheme, we can use the currently set iRODS password to create .irodsA file from Python thus: diff --git a/irods/client_init.py b/irods/client_init.py index a1c96e89..68e1d687 100644 --- a/irods/client_init.py +++ b/irods/client_init.py @@ -1,34 +1,65 @@ +import contextlib +import getpass +import os +import sys + from irods import (env_filename_from_keyword_args, derived_auth_filename) import irods.client_configuration as cfg import irods.password_obfuscation as obf import irods.helpers as h -import getpass -import os -import sys -def write_native_credentials_to_secrets_file(password, **kw): - env_file = env_filename_from_keyword_args(kw) - auth_file = derived_auth_filename(env_file) - old_mask = None +@contextlib.contextmanager +def _open_file_for_protected_contents(file_path, *arg, **kw): + f = old_mask = None try: old_mask = os.umask(0o77) - open(auth_file,'w').write(obf.encode(password)) + f = open(file_path, *arg, **kw) + yield f finally: if old_mask is not None: os.umask(old_mask) - - return True + if f is not None: + f.close() + +class irodsA_already_exists(Exception): + pass + +def _write_encoded_auth_value(auth_file, encode_input, overwrite): + if not auth_file: + raise RuntimeError(f'Path to irodsA ({auth_file}) is null.') + if not overwrite and os.path.exists(auth_file): + raise irodsA_already_exists(f'Overwriting not enabled and {auth_file} already exists.') + with _open_file_for_protected_contents(auth_file, 'w') as irodsA: + irodsA.write(obf.encode(encode_input)) + +def write_native_credentials_to_secrets_file(password, overwrite = True, **kw): + """Write the credentials to an .irodsA file that will enable logging in with native authentication + using the given cleartext password. + + If overwrite is False, irodsA_already_exists will be raised if an .irodsA is found at the + expected path. + """ + env_file = env_filename_from_keyword_args(kw) + auth_file = derived_auth_filename(env_file) + _write_encoded_auth_value(auth_file, password, overwrite) + +def write_pam_credentials_to_secrets_file(password, overwrite = True, **kw): + """Write the credentials to an .irodsA file that will enable logging in with PAM authentication + using the given cleartext password. -def write_pam_credentials_to_secrets_file( password ,**kw): + If overwrite is False, irodsA_already_exists will be raised if an .irodsA is found at the + expected path. + """ s = h.make_session() s.pool.account.password = password + to_encode = [] with cfg.loadlines( [dict(setting='legacy_auth.pam.password_for_auto_renew',value=None), dict(setting='legacy_auth.pam.store_password_to_environment',value=False)] ): to_encode = s.pam_pw_negotiated - if to_encode: - open(s.pool.account.derived_auth_file,'w').write(obf.encode(to_encode[0])) - return True - return False + if not to_encode: + raise RuntimeError(f'Password token was not passed from server.') + auth_file = s.pool.account.derived_auth_file + _write_encoded_auth_value(auth_file, to_encode[0], overwrite) if __name__ == '__main__': vector = { diff --git a/irods/test/scripts/test002_write_native_credentials_to_secrets_file.bats b/irods/test/scripts/test002_write_native_credentials_to_secrets_file.bats index dc1837d2..c30f3aa8 100755 --- a/irods/test/scripts/test002_write_native_credentials_to_secrets_file.bats +++ b/irods/test/scripts/test002_write_native_credentials_to_secrets_file.bats @@ -1,7 +1,7 @@ #!/usr/bin/env bats # # Test creation of .irodsA for iRODS native authentication using the free function, -# irods.client_init.write_pam_credentials_to_secrets_file +# irods.client_init.write_native_credentials_to_secrets_file . "$BATS_TEST_DIRNAME"/test_support_functions PYTHON=python3 @@ -10,26 +10,18 @@ PYTHON=python3 # Run as ubuntu user with sudo; python_irodsclient must be installed (in either ~/.local or a virtualenv) # -ALICES_OLD_PAM_PASSWD="test123" -ALICES_NEW_PAM_PASSWD="new_pass" +@test create_irods_secrets_file { -setup() -{ - setup_pam_login_for_alice "$ALICES_OLD_PAM_PASSWD" -} - -teardown() -{ - finalize_pam_login_for_alice - test_specific_cleanup -} - -@test create_secrets_file { - - # Old .irodsA is already created, so we delete it and alter the pam password. - sudo chpasswd <<<"alice:$ALICES_NEW_PAM_PASSWD" - rm -f ~/.irods/.irodsA - $PYTHON -c "import irods.client_init; irods.client_init.write_pam_credentials_to_secrets_file('$ALICES_NEW_PAM_PASSWD')" + rm -fr ~/.irods + mkdir ~/.irods + cat > ~/.irods/irods_environment.json <<-EOF + { "irods_host":"$(hostname)", + "irods_port":1247, + "irods_user_name":"rods", + "irods_zone_name":"tempZone" + } + EOF + $PYTHON -c "import irods.client_init; irods.client_init.write_native_credentials_to_secrets_file('rods')" # Define the core Python to be run, basically a minimal code block ensuring that we can authenticate to iRODS # without an exception being raised. @@ -42,6 +34,5 @@ print ('env_auth_scheme=%s' % ses.pool.account._original_authentication_scheme) " OUTPUT=$($PYTHON -c "$SCRIPT") # Assert passing value - [ $OUTPUT = "env_auth_scheme=pam_password" ] - + [ $OUTPUT = "env_auth_scheme=native" ] } diff --git a/irods/test/scripts/test003_write_pam_credentials_to_secrets_file.bats b/irods/test/scripts/test003_write_pam_credentials_to_secrets_file.bats index 4686755f..1747a4d2 100755 --- a/irods/test/scripts/test003_write_pam_credentials_to_secrets_file.bats +++ b/irods/test/scripts/test003_write_pam_credentials_to_secrets_file.bats @@ -1,7 +1,7 @@ #!/usr/bin/env bats # # Test creation of .irodsA for iRODS pam_password authentication using the free function, -# irods.client_init.write_native_credentials_to_secrets_file +# irods.client_init.write_pam_credentials_to_secrets_file . "$BATS_TEST_DIRNAME"/test_support_functions PYTHON=python3 @@ -10,18 +10,26 @@ PYTHON=python3 # Run as ubuntu user with sudo; python_irodsclient must be installed (in either ~/.local or a virtualenv) # -@test create_irods_secrets_file { +ALICES_OLD_PAM_PASSWD="test123" +ALICES_NEW_PAM_PASSWD="new_pass" - rm -fr ~/.irods - mkdir ~/.irods - cat > ~/.irods/irods_environment.json <<-EOF - { "irods_host":"$(hostname)", - "irods_port":1247, - "irods_user_name":"rods", - "irods_zone_name":"tempZone" - } - EOF - $PYTHON -c "import irods.client_init; irods.client_init.write_native_credentials_to_secrets_file('rods')" +setup() +{ + setup_pam_login_for_alice "$ALICES_OLD_PAM_PASSWD" +} + +teardown() +{ + finalize_pam_login_for_alice + test_specific_cleanup +} + +@test create_secrets_file { + + # Old .irodsA is already created, so we delete it and alter the pam password. + sudo chpasswd <<<"alice:$ALICES_NEW_PAM_PASSWD" + rm -f ~/.irods/.irodsA + $PYTHON -c "import irods.client_init; irods.client_init.write_pam_credentials_to_secrets_file('$ALICES_NEW_PAM_PASSWD')" # Define the core Python to be run, basically a minimal code block ensuring that we can authenticate to iRODS # without an exception being raised. @@ -34,5 +42,6 @@ print ('env_auth_scheme=%s' % ses.pool.account._original_authentication_scheme) " OUTPUT=$($PYTHON -c "$SCRIPT") # Assert passing value - [ $OUTPUT = "env_auth_scheme=native" ] + [[ $OUTPUT = "env_auth_scheme=pam"* ]] + }