diff --git a/README.md b/README.md index 7b260b75c..68fd0f6d8 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,17 @@ iCommands. Caveat for iRODS 4.3+: when upgrading from 4.2, the "irods_authentication_scheme" setting must be changed from "pam" to "pam_password" in `~/.irods/irods_environment.json` for all file-based client environments. +To replicate iinit's capability for creating valid PAM login credentials file (.irodsA) for the client login environment, we can set these +two configuration variables: + +``` +legacy_auth.pam.password_for_auto_renew "my_pam_password" +legacy_auth.pam.store_password_to_environment True +``` + +Optionally, the `legacy_auth.pam.time_to_live_in_hours` may also be set to determine the time-to-live for the new password. +Leaving it at the default value defers this decision to the server. + Maintaining a connection ------------------------ diff --git a/irods/account.py b/irods/account.py index 9c96cb485..430bd599d 100644 --- a/irods/account.py +++ b/irods/account.py @@ -1,15 +1,25 @@ +import os + class iRODSAccount(object): + @property + def derived_auth_file(self): + return '' if not self.env_file else os.path.join(os.path.dirname(self.env_file),'.irodsA') + def __init__(self, irods_host, irods_port, irods_user_name, irods_zone_name, irods_authentication_scheme='native', password=None, client_user=None, - server_dn=None, client_zone=None, **kwargs): + server_dn=None, client_zone=None, + env_file = '', + **kwargs): + # Allowed overrides when cloning sessions. (Currently hostname only.) for k,v in kwargs.pop('_overrides',{}).items(): if k =='irods_host': irods_host = v + self.env_file = env_file tuplify = lambda _: _ if isinstance(_,(list,tuple)) else (_,) schemes = [_.lower() for _ in tuplify(irods_authentication_scheme)] diff --git a/irods/client_init.py b/irods/client_init.py new file mode 100644 index 000000000..5fca637c5 --- /dev/null +++ b/irods/client_init.py @@ -0,0 +1,33 @@ +import irods.client_configuration as cfg +import irods.password_obfuscation as obf +import irods.helpers as h +import getpass +import sys + +def write_credentials_with_native_password( password ): + s = h.make_session() + assert(not s.auth_file) + open(s.pool.account.derived_auth_file,'w').write(obf.encode(password)) + return True + +def write_credentials_with_pam_password( password ): + s = h.make_session() + assert(not s.auth_file) + s.pool.account.password = password + with cfg.loadlines( [dict(setting='legacy_auth.pam.password_for_auto_renew',value='')] ): + 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 __name__ == '__main__': + vector = { + 'pam': write_credentials_with_pam_password, + 'native': write_credentials_with_native_password, + } + + if sys.argv[1] in vector: + vector[sys.argv[1]](getpass.getpass(prompt=f'{sys.argv[1]} password: ')) + else: + print('did not recognize authentication scheme argument',file = sys.stderr) diff --git a/irods/connection.py b/irods/connection.py index a40c712c9..4bc3bdcce 100644 --- a/irods/connection.py +++ b/irods/connection.py @@ -532,8 +532,9 @@ def _login_pam(self): self._login_native(password = auth_out.result_) # Store new password in .irodsA if requested. - if self.account._auth_file and cfg.legacy_auth.pam.store_password_to_environment: - with open(self.account._auth_file,'w') as f: + auth_file = (self.account._auth_file or self.account.derived_auth_file) + if auth_file and cfg.legacy_auth.pam.store_password_to_environment: + with open(auth_file,'w') as f: f.write(obf.encode(auth_out.result_)) logger.debug('new PAM pw write succeeded') diff --git a/irods/session.py b/irods/session.py index 6bee2c313..03ace0ca8 100644 --- a/irods/session.py +++ b/irods/session.py @@ -210,10 +210,9 @@ def cleanup(self, new_host = ''): self.__configured = self.configure(**self.do_configure) def _configure_account(self, **kwargs): - + env_file = None try: env_file = kwargs['irods_env_file'] - except KeyError: # For backward compatibility for key in ['host', 'port', 'authentication_scheme']: @@ -232,6 +231,9 @@ def _configure_account(self, **kwargs): # Update with new keywords arguments only creds.update((key, value) for key, value in kwargs.items() if key not in creds) + if env_file: + creds['env_file'] = env_file + # Get auth scheme try: auth_scheme = creds['irods_authentication_scheme'] @@ -259,10 +261,11 @@ def _configure_account(self, **kwargs): missing_file_path = [] error_args = [] pw = creds['password'] = self.get_irods_password(session_ = self, file_path_if_not_found = missing_file_path, **creds) - if not pw and creds.get('irods_user_name') != 'anonymous': - if missing_file_path: - error_args += ["Authentication file not found at {!r}".format(missing_file_path[0])] - raise NonAnonymousLoginWithoutPassword(*error_args) + if auth_scheme.lower() not in PAM_AUTH_SCHEMES: + if not pw and creds.get('irods_user_name') != 'anonymous': + if missing_file_path: + error_args += ["Authentication file not found at {!r}".format(missing_file_path[0])] + raise NonAnonymousLoginWithoutPassword(*error_args) return iRODSAccount(**creds)