from __future__ import absolute_import
import atexit
import os
import ast
import json
import errno
import logging
from irods.query import Query
from irods.pool import Pool
from irods.account import iRODSAccount
from irods.manager.collection_manager import CollectionManager
from irods.manager.data_object_manager import DataObjectManager
from irods.manager.metadata_manager import MetadataManager
from irods.manager.access_manager import AccessManager
from irods.manager.user_manager import UserManager, GroupManager
from irods.manager.resource_manager import ResourceManager
from irods.manager.zone_manager import ZoneManager
from irods.exception import NetworkException
from irods.password_obfuscation import decode
from irods import NATIVE_AUTH_SCHEME, PAM_AUTH_SCHEME
import threading
import weakref
from . import DEFAULT_CONNECTION_TIMEOUT
_sessions = None
_sessions_lock = threading.Lock()
def _cleanup_remaining_sessions():
for ses in _sessions.copy():
ses.cleanup() # internally modifies _sessions
def _weakly_reference(ses):
global _sessions
try:
if _sessions is None:
with _sessions_lock:
do_register = (_sessions is None)
if do_register:
_sessions = weakref.WeakKeyDictionary()
atexit.register(_cleanup_remaining_sessions)
finally:
_sessions[ses] = None
logger = logging.getLogger(__name__)
class NonAnonymousLoginWithoutPassword(RuntimeError): pass
class iRODSSession(object):
@property
def env_file (self):
return self._env_file
@property
def auth_file (self):
return self._auth_file
# session.acls will act identically to session.permissions, except its `get'
# method has a default parameter of report_raw_acls = True, so it enumerates
# ACLs exactly in the manner of "ils -A".
@property
def available_permissions(self):
from irods.access import (iRODSAccess,_iRODSAccess_pre_4_3_0)
try:
self.__access
except AttributeError:
self.__access = _iRODSAccess_pre_4_3_0 if self.server_version < (4,3) else iRODSAccess
return self.__access
@property
def groups(self):
class _GroupManager(self.user_groups.__class__):
def create(self, name,
group_admin = None): # NB new default (see user_groups manager and i/f, with False as default)
user_type = 'rodsgroup' # These are no longer parameters in the new interface, as they have no reason to vary.
user_zone = "" # Groups (1) are always of type 'rodsgroup', (2) always belong to the local zone, and
auth_str = "" # (3) do not authenticate.
return super(_GroupManager, self).create(name,
user_type,
user_zone,
auth_str,
group_admin,
suppress_deprecation_warning = True)
def addmember(self, group_name,
user_name,
user_zone = "",
group_admin = None):
return super(_GroupManager, self).addmember(group_name,
user_name,
user_zone,
group_admin,
suppress_deprecation_warning = True)
def removemember(self, group_name,
user_name,
user_zone = "",
group_admin = None):
return super(_GroupManager, self).removemember(group_name,
user_name,
user_zone,
group_admin,
suppress_deprecation_warning = True)
_groups = getattr(self,'_groups',None)
if not _groups:
_groups = self._groups = _GroupManager(self.user_groups.sess)
return self._groups
@property
def acls(self):
class ACLs(self.permissions.__class__):
def set(self, acl, recursive=False, admin=False, **kw):
kw['suppress_deprecation_warning'] = True
return super(ACLs, self).set(acl, recursive=recursive, admin=admin, **kw)
def get(self, target, **kw):
kw['suppress_deprecation_warning'] = True
return super(ACLs,self).get(target, report_raw_acls = True, **kw)
_acls = getattr(self,'_acls',None)
if not _acls: _acls = self._acls = ACLs(self.permissions.sess)
return _acls
def __init__(self, configure = True, auto_cleanup = True, **kwargs):
self.pool = None
self.numThreads = 0
self._env_file = ''
self._auth_file = ''
self.do_configure = (kwargs if configure else {})
self._cached_connection_timeout = kwargs.pop('connection_timeout', DEFAULT_CONNECTION_TIMEOUT)
self.__configured = None
if configure:
self.__configured = self.configure(**kwargs)
self.collections = CollectionManager(self)
self.data_objects = DataObjectManager(self)
self.metadata = MetadataManager(self)
self.permissions = AccessManager(self)
self.users = UserManager(self)
self.user_groups = GroupManager(self)
self.resources = ResourceManager(self)
self.zones = ZoneManager(self)
if auto_cleanup:
_weakly_reference(self)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.cleanup()
def __del__(self):
self.do_configure = {}
# If self.pool has been fully initialized (ie. no exception was
# raised during __init__), then try to clean up.
if self.pool is not None:
self.cleanup()
def cleanup(self):
for conn in self.pool.active | self.pool.idle:
try:
conn.disconnect()
except NetworkException:
pass
conn.release(True)
if self.do_configure:
self.__configured = self.configure(**self.do_configure)
def _configure_account(self, **kwargs):
try:
env_file = kwargs['irods_env_file']
except KeyError:
# For backward compatibility
for key in ['host', 'port', 'authentication_scheme']:
if key in kwargs:
kwargs['irods_{}'.format(key)] = kwargs.pop(key)
for key in ['user', 'zone']:
if key in kwargs:
kwargs['irods_{}_name'.format(key)] = kwargs.pop(key)
return iRODSAccount(**kwargs)
# Get credentials from irods environment file
creds = self.get_irods_env(env_file, session_ = self)
# Update with new keywords arguments only
creds.update((key, value) for key, value in kwargs.items() if key not in creds)
# Get auth scheme
try:
auth_scheme = creds['irods_authentication_scheme']
except KeyError:
# default
auth_scheme = 'native'
if auth_scheme.lower() == PAM_AUTH_SCHEME:
if 'password' in creds:
return iRODSAccount(**creds)
else:
# password will be from irodsA file therefore use native login
creds['irods_authentication_scheme'] = NATIVE_AUTH_SCHEME
elif auth_scheme != 'native':
return iRODSAccount(**creds)
# Native auth, try to unscramble password
try:
creds['irods_authentication_uid'] = kwargs['irods_authentication_uid']
except KeyError:
pass
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)
return iRODSAccount(**creds)
def configure(self, **kwargs):
account = self.__configured
if not account:
account = self._configure_account(**kwargs)
connection_refresh_time = self.get_connection_refresh_time(**kwargs)
logger.debug("In iRODSSession's configure(). connection_refresh_time set to {}".format(connection_refresh_time))
self.pool = Pool(account, application_name=kwargs.pop('application_name',''), connection_refresh_time=connection_refresh_time)
conn_timeout = getattr(self,'_cached_connection_timeout',None)
self.pool.connection_timeout = conn_timeout
return account
def query(self, *args):
return Query(self, *args)
@property
def username(self):
return self.pool.account.client_user
@property
def zone(self):
return self.pool.account.client_zone
@property
def host(self):
return self.pool.account.host
@property
def port(self):
return self.pool.account.port
@property
def server_version(self):
try:
reported_vsn = os.environ.get("PYTHON_IRODSCLIENT_REPORTED_SERVER_VERSION","")
return tuple(ast.literal_eval(reported_vsn))
except SyntaxError: # environment variable was malformed, empty, or unset
return self.__server_version()
def __server_version(self):
try:
conn = next(iter(self.pool.active))
return conn.server_version
except StopIteration:
conn = self.pool.get_connection()
version = conn.server_version
conn.release()
return version
@property
def pam_pw_negotiated(self):
self.pool.account.store_pw = []
conn = self.pool.get_connection()
pw = getattr(self.pool.account,'store_pw',[])
delattr( self.pool.account, 'store_pw')
conn.release()
return pw
@property
def default_resource(self):
return self.pool.account.default_resource
@default_resource.setter
def default_resource(self, name):
self.pool.account.default_resource = name
@property
def connection_timeout(self):
return self.pool.connection_timeout
@connection_timeout.setter
def connection_timeout(self, seconds):
self._cached_connection_timeout = seconds
if seconds is not None:
self.pool.connection_timeout = seconds
@staticmethod
def get_irods_password_file():
try:
return os.environ['IRODS_AUTHENTICATION_FILE']
except KeyError:
return os.path.expanduser('~/.irods/.irodsA')
@staticmethod
def get_irods_env(env_file, session_ = None):
try:
with open(env_file, 'rt') as f:
j = json.load(f)
if session_ is not None:
session_._env_file = env_file
return j
except IOError:
logger.debug("Could not open file {}".format(env_file))
return {}
@staticmethod
def get_irods_password(session_ = None, file_path_if_not_found = (), **kwargs):
path_memo = []
try:
irods_auth_file = kwargs['irods_authentication_file']
except KeyError:
irods_auth_file = iRODSSession.get_irods_password_file()
try:
uid = kwargs['irods_authentication_uid']
except KeyError:
uid = None
_retval = ''
try:
with open(irods_auth_file, 'r') as f:
_retval = decode(f.read().rstrip('\n'), uid)
return _retval
except IOError as exc:
if exc.errno != errno.ENOENT:
raise # Auth file exists but can't be read
path_memo = [ irods_auth_file ]
return '' # No auth file (as with anonymous user)
finally:
if isinstance(file_path_if_not_found, list) and path_memo:
file_path_if_not_found[:] = path_memo
if session_ is not None and _retval:
session_._auth_file = irods_auth_file
def get_connection_refresh_time(self, **kwargs):
connection_refresh_time = -1
connection_refresh_time = int(kwargs.get('refresh_time', -1))
if connection_refresh_time != -1:
return connection_refresh_time
try:
env_file = kwargs['irods_env_file']
except KeyError:
return connection_refresh_time
if env_file is not None:
env_file_map = self.get_irods_env(env_file)
connection_refresh_time = int(env_file_map.get('irods_connection_refresh_time', -1))
if connection_refresh_time < 1:
# Negative values are not allowed.
logger.debug('connection_refresh_time in {} file has value of {}. Only values greater than 1 are allowed.'.format(env_file, connection_refresh_time))
connection_refresh_time = -1
return connection_refresh_time