Skip to content

Commit f2fb602

Browse files
committed
[_499,_688] collapsed review changes
whitespace, documentation, comments review changes nr. 1 2/17/25 2:07am return early if calling into 4.2 version of func. mv attr change attr_changed usage replaced with older version 1 level indir ->callable info->debug auth modules get local loggers more doc/msg chg explanation/underscore address other comments del attr_changed
1 parent 62f0066 commit f2fb602

File tree

14 files changed

+111
-138
lines changed

14 files changed

+111
-138
lines changed

irods/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ def derived_auth_filename(env_filename):
3030
if not env_filename:
3131
return ""
3232
default_irods_authentication_file = (
33-
##->https://github.com/irods/python-irodsclient/issues/686
34-
#os.path.join(os.path.dirname(env_filename), ".irodsA")
33+
# TODO(#686): move into separate commit linked to this issue.
34+
# This is a revision in how we determine the default value of the authentication file (.irodsA) path.
35+
# Previously this was calculated as:
36+
# os.path.join(os.path.dirname(env_filename), ".irodsA")
3537
os.path.expanduser("~/.irods/.irodsA")
3638
)
3739
return os.environ.get(

irods/auth/__init__.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,27 @@
1313
AUTH_PLUGIN_PACKAGE = "irods.auth"
1414

1515

16-
NoneType = type(None)
16+
# Python3 does not have types.NoneType
17+
_NoneType = type(None)
1718

1819

1920
class AuthStorage:
21+
"""A class that facilitates flexible means password storage.
22+
23+
Using an instance of this class, passwords may either be
24+
25+
- directly placed in a member attribute (pw), or
26+
27+
- they may be written to / read from a specified file path in encoded
28+
form, usually in an .irodsA file intended for iRODS client authentication.
29+
30+
Most typical of this class's utility is the transfer of password information from
31+
the pam_password to the native authentication flow. In this usage, whether the
32+
password is stored in RAM or in the filesystem depends on whether it was read
33+
originally as a function parameter or from an authentication file, respectively,
34+
when the session was created.
35+
36+
"""
2037

2138
@staticmethod
2239
def get_env_password(filename = None):
@@ -38,12 +55,20 @@ def set_env_password(unencoded_pw, filename = None):
3855

3956
@staticmethod
4057
def get_temp_pw_storage(conn):
58+
"""Fetch the AuthStorage instance associated with this connection object.
59+
"""
4160
return getattr(conn,'auth_storage',lambda:None)()
4261

4362
@staticmethod
4463
def create_temp_pw_storage(conn):
45-
"""A reference to the value returned by this call should be stored for the duration of the
46-
authentication exchange.
64+
"""Creates an AuthStorage instance to be cached and associated with this connection object.
65+
66+
Called multiple times for the same connection, it will return the cached instance.
67+
68+
The value returned by this call should be stored by the caller into an appropriately scoped
69+
variable to ensure the AuthStorage instance endures for the desired lifetime -- that is,
70+
for however long we wish to keep the password information around. This is because the
71+
connection object only maintains a weak reference to said instance.
4772
"""
4873
store = getattr(conn,'auth_storage',None)
4974
if store is None:
@@ -64,7 +89,9 @@ def auth_file(self):
6489
return self._auth_file or self.conn.account.derived_auth_file
6590

6691
def use_client_auth_file(self, auth_file):
67-
if isinstance(auth_file, (str, NoneType)):
92+
"""Set to None to completely suppress use of an .irodsA auth file.
93+
"""
94+
if isinstance(auth_file, (str, _NoneType)):
6895
self._auth_file = auth_file
6996
else:
7097
msg = f"Invalid object in {self.__class__}._auth_file"
@@ -132,17 +159,17 @@ def __init__(self, connection, scheme):
132159
def call(self, next_operation, request):
133160
logging.info('next operation = %r', next_operation)
134161
old_func = func = next_operation
135-
while isinstance(func, str):
162+
# One level of indirection should be sufficient to get a callable method.
163+
if not callable(func):
136164
old_func, func = (func, getattr(self, func, None))
137165
func = (func or old_func)
138-
if not func:
166+
if not callable(func):
139167
raise RuntimeError("client request contains no callable 'next_operation'")
140168
resp = func(request)
141169
logging.info('resp = %r',resp)
142170
return resp
143171

144172
def authenticate_client(self, next_operation = "auth_client_start", initial_request = {}):
145-
146173
to_send = initial_request.copy()
147174
to_send["scheme"] = self.scheme
148175

irods/auth/native.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,42 @@
1212

1313

1414
def login(conn, **extra_opt):
15+
"""When the Python iRODS client loads this(or any plugin) for authenticating a connection object,
16+
login is the hook function that gets called.
17+
"""
1518
opt = {'user_name': conn.account.proxy_user,
1619
'zone_name': conn.account.proxy_zone}
1720
opt.update(extra_opt)
18-
authenticate_native(conn, req = opt)
21+
_authenticate_native(conn, req = opt)
1922

2023

2124
_scheme = 'native'
2225

2326

24-
def authenticate_native(conn, req):
27+
_logger = logging.getLogger(__name__)
2528

26-
logging.info('----------- %s (begin)', _scheme)
2729

28-
native_ClientAuthState(
30+
def _authenticate_native(conn, req):
31+
"""The implementation for the client side of a native scheme authentication flow.
32+
It is called by login(), the external hook.
33+
Other client auth plugins should at least roughly follow this pattern.
34+
"""
35+
_logger.debug('----------- %s (begin)', _scheme)
36+
37+
_native_ClientAuthState(
2938
conn,
3039
scheme = _scheme
3140
).authenticate_client(
3241
# initial_request is called context (or ctx for short) in iRODS core library code.
3342
initial_request = req
3443
)
3544

36-
logging.info('----------- %s (end)', _scheme)
37-
45+
_logger.debug('----------- %s (end)', _scheme)
3846

39-
class native_ClientAuthState(authentication_base):
4047

41-
def auth_client_start(self, request):
42-
resp = request.copy()
43-
# user_name and zone_name keys injected by authenticate_client() method
44-
resp[__NEXT_OPERATION__] = self.AUTH_CLIENT_AUTH_REQUEST # native_auth_client_request
45-
return resp
48+
class _native_ClientAuthState(authentication_base):
49+
"""A class containing the specific methods needed to implement a native scheme authentication flow.
50+
"""
4651

4752
# Client defines. These strings should match instance method names within the class namespace.
4853
AUTH_AGENT_START = 'native_auth_agent_start'
@@ -54,6 +59,12 @@ def auth_client_start(self, request):
5459
AUTH_AGENT_AUTH_REQUEST = "auth_agent_auth_request"
5560
AUTH_AGENT_AUTH_RESPONSE = "auth_agent_auth_response"
5661

62+
def auth_client_start(self, request):
63+
resp = request.copy()
64+
# user_name and zone_name keys injected by authenticate_client() method
65+
resp[__NEXT_OPERATION__] = self.AUTH_CLIENT_AUTH_REQUEST # native_auth_client_request
66+
return resp
67+
5768
def native_auth_client_request(self, request):
5869
server_req = request.copy()
5970
server_req[__NEXT_OPERATION__] = self.AUTH_AGENT_AUTH_REQUEST

irods/auth/pam_password.py

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ def login(conn, **extra_opt):
2323
_scheme = 'pam_password'
2424

2525

26+
_logger = logging.getLogger(__name__)
27+
28+
2629
def authenticate_pam_password(conn, req):
27-
logging.info('----------- %s (begin)', _scheme)
30+
_logger.debug('----------- %s (begin)', _scheme)
2831

2932
# By design, we persist this "depot" object over the whole of the authentication
3033
# exchange with the iRODS server as a means of sending password information to the
@@ -33,14 +36,14 @@ def authenticate_pam_password(conn, req):
3336
# are authenticating without the help of iCommand-type client env/auth files.
3437
_ = AuthStorage.create_temp_pw_storage(conn)
3538

36-
pam_password_ClientAuthState(
39+
_pam_password_ClientAuthState(
3740
conn,
3841
scheme = _scheme
3942
).authenticate_client(
4043
initial_request = req
4144
)
4245

43-
logging.info('----------- %s (end)', _scheme)
46+
_logger.debug('----------- %s (end)', _scheme)
4447

4548

4649
def get_pam_password_from_stdin(file_like_object = None):
@@ -61,30 +64,39 @@ def get_pam_password_from_stdin(file_like_object = None):
6164
AUTH_PASSWORD_KEY = "a_pw"
6265

6366

64-
class pam_password_ClientAuthState(authentication_base):
67+
class _pam_password_ClientAuthState(authentication_base):
68+
69+
# Client define
70+
AUTH_CLIENT_AUTH_REQUEST = "pam_password_auth_client_request"
71+
72+
# Server define
73+
AUTH_AGENT_AUTH_REQUEST = "auth_agent_auth_request"
6574

6675
def __init__(self,*_,check_ssl=True,**_kw):
6776
super().__init__(*_,**_kw)
6877
self.check_ssl = check_ssl
69-
self._l = None
78+
self._list_for_request_result_return = None
7079

7180
def auth_client_start(self, request):
7281

73-
self._l = request.pop(CLIENT_GET_REQUEST_RESULT, False)
82+
# This list reference is popped and cached for the purpose of returning the request_result value
83+
# to the caller upon request.
84+
self._list_for_request_result_return = request.pop(CLIENT_GET_REQUEST_RESULT, False)
7485

7586
if self.check_ssl:
7687
if not isinstance(self.conn.socket, ssl.SSLSocket):
77-
msg = 'Need to be connected via SSL.'
88+
msg = "pam_password auth scheme requires secure communications (TLS/SSL) with the server."
7889
raise RuntimeError(msg)
7990

8091
resp = request.copy()
8192

82-
obj = resp.pop(FORCE_PASSWORD_PROMPT, None)
93+
password_input_obj = resp.pop(FORCE_PASSWORD_PROMPT, None)
8394

84-
if obj:
85-
obj = None if isinstance(obj,(int,bool)) else obj
86-
# Like with the C++ plugin, we offer the user a chance
87-
resp[AUTH_PASSWORD_KEY] = get_pam_password_from_stdin(file_like_object = obj)
95+
if password_input_obj:
96+
if isinstance(password_input_obj,(int,bool)):
97+
password_input_obj = None
98+
# Like with the C++ plugin, we offer the user a chance to enter a password.
99+
resp[AUTH_PASSWORD_KEY] = get_pam_password_from_stdin(file_like_object = password_input_obj)
88100
else:
89101
# Password from .irodsA in environment.
90102
if self.conn.account._auth_file:
@@ -97,12 +109,6 @@ def auth_client_start(self, request):
97109
resp[__NEXT_OPERATION__] = self.AUTH_CLIENT_AUTH_REQUEST
98110
return resp
99111

100-
# Client define
101-
AUTH_CLIENT_AUTH_REQUEST = "pam_password_auth_client_request"
102-
103-
# Server define
104-
AUTH_AGENT_AUTH_REQUEST = "auth_agent_auth_request"
105-
106112
def pam_password_auth_client_request(self, request):
107113
server_req = request.copy()
108114
server_req[__NEXT_OPERATION__] = self.AUTH_AGENT_AUTH_REQUEST
@@ -113,14 +119,15 @@ def pam_password_auth_client_request(self, request):
113119
depot = AuthStorage.get_temp_pw_storage(self.conn)
114120
if depot:
115121
if resp.get(STORE_PASSWORD_IN_MEMORY, None):
122+
# Prevent use of an .irodsA to store an encoded password.
116123
depot.use_client_auth_file(None)
117124
depot.store_pw(resp["request_result"])
118125
else:
119126
msg = "auth storage object was either not set, or allowed to expire prematurely."
120127
raise RuntimeError(msg)
121128

122-
if isinstance(self._l,list):
123-
self._l[:] = (resp["request_result"],)
129+
if isinstance(self._list_for_request_result_return,list):
130+
self._list_for_request_result_return[:] = (resp["request_result"],)
124131

125132
resp[__NEXT_OPERATION__] = self.perform_native_auth
126133
return resp

irods/client_init.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,15 @@ def write_pam_irodsA_file(password, overwrite=True, ttl = '', **kw):
6565
import io
6666
ses = kw.pop('_session', None) or h.make_session(**kw)
6767
if ses._server_version(iRODSSession.RAW_SERVER_VERSION) < (4,3):
68-
write_pam_credentials_to_secrets_file(
68+
return write_pam_credentials_to_secrets_file(
6969
password,
7070
overwrite = overwrite,
7171
ttl = ttl,
7272
_session = ses)
7373

7474
auth_file = ses.pool.account.derived_auth_file
7575
if not auth_file:
76-
msg = "Need an active client environment"
76+
msg = "Auth file could not be written because no iRODS client environment was found."
7777
raise RuntimeError(msg)
7878
if ttl:
7979
ses.set_auth_option_for_scheme('pam_password', irods.auth.pam_password.AUTH_TTL_KEY, ttl)

irods/helpers/__init__.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,20 @@ def xml_mode(s):
3838
ET(None)
3939

4040

41+
class _unlikely_value:
42+
pass
43+
4144
@contextlib.contextmanager
42-
def attr_changed(obj, attrname, value):
43-
old = getattr(obj, attrname, None)
44-
try:
45-
setattr(obj, attrname, value)
46-
yield
47-
finally:
48-
setattr(obj, attrname, old)
45+
def temporarily_assign_attribute(
46+
target, attr, value, not_set_indicator=_unlikely_value()
47+
):
48+
save = not_set_indicator
49+
try:
50+
save = getattr(target, attr, not_set_indicator)
51+
setattr(target, attr, value)
52+
yield
53+
finally:
54+
if save != not_set_indicator:
55+
setattr(target, attr, save)
56+
else:
57+
delattr(target, attr)

irods/message/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,7 @@ class MetadataRequest(Message):
772772
def __init__(self, *args, **metadata_opts):
773773
super(MetadataRequest, self).__init__()
774774

775+
# Python3 does not have types.NoneType
775776
NoneType = type(None)
776777

777778
def field_name(i):

irods/pool.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
from irods import DEFAULT_CONNECTION_TIMEOUT
99
from irods.connection import Connection
10-
#import irods.helpers
1110
from irods.ticket import Ticket
1211

1312
logger = logging.getLogger(__name__)
@@ -71,7 +70,7 @@ def __init__(
7170
@contextlib.contextmanager
7271
def no_auto_authenticate(self):
7372
import irods.helpers
74-
with irods.helpers.attr_changed(self, '_need_auth', False):
73+
with irods.helpers.temporarily_assign_attribute(self, '_need_auth', False):
7574
yield self
7675

7776
def set_session_ref(self, session):

irods/session.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import os
1010
import threading
1111
import weakref
12-
import irods.auth
12+
import irods.auth
1313
from irods.query import Query
1414
from irods.genquery2 import GenQuery2
1515
from irods.pool import Pool
@@ -71,16 +71,6 @@ class NonAnonymousLoginWithoutPassword(RuntimeError):
7171
pass
7272

7373

74-
@contextlib.contextmanager
75-
def attr_changed(obj, attrname, value):
76-
old = getattr(obj, attrname, None)
77-
try:
78-
setattr(obj, attrname, value)
79-
yield
80-
finally:
81-
setattr(obj, attrname, old)
82-
83-
8474
class iRODSSession:
8575

8676
def library_features(self):

irods/test/connection_test.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,8 @@
1212
)
1313
import irods.session
1414
import irods.test.helpers as helpers
15-
from irods.test.helpers import (
16-
server_side_sleep,
17-
temporarily_assign_attribute as temp_setter,
18-
)
19-
15+
from irods.test.helpers import server_side_sleep
16+
from irods.helpers import temporarily_assign_attribute as temp_setter
2017

2118
class TestConnections(unittest.TestCase):
2219

0 commit comments

Comments
 (0)