1+ import importlib
2+ import logging
3+ import weakref
4+ from irods .api_number import api_number
5+ from irods .message import iRODSMessage , JSON_Message
6+ import irods .password_obfuscation as obf
7+ import irods .session
8+
9+
110__all__ = ["pam_password" , "native" ]
211
12+
313AUTH_PLUGIN_PACKAGE = "irods.auth"
414
5- import importlib
15+
16+ NoneType = type (None )
17+
18+
19+ class AuthStorage :
20+
21+ @staticmethod
22+ def get_env_password (filename = None ):
23+ options = dict (irods_authentication_file = filename ) if filename else {}
24+ return irods .session .iRODSSession .get_irods_password (** options )
25+
26+ @staticmethod
27+ def get_env_password_file ():
28+ return irods .session .iRODSSession .get_irods_password_file ()
29+
30+ @staticmethod
31+ def set_env_password (unencoded_pw , filename = None ):
32+ if filename is None :
33+ filename = AuthStorage .get_env_password_file ()
34+ from ..client_init import _open_file_for_protected_contents
35+ with _open_file_for_protected_contents (filename ,'w' ) as irodsA :
36+ irodsA .write (obf .encode (unencoded_pw ))
37+ return filename
38+
39+ @staticmethod
40+ def get_temp_pw_storage (conn ):
41+ return getattr (conn ,'auth_storage' ,lambda :None )()
42+
43+ @staticmethod
44+ 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.
47+ """
48+ store = getattr (conn ,'auth_storage' ,None )
49+ if store is None :
50+ store = AuthStorage (conn )
51+ # So that the connection object doesn't hold on to password data too long:
52+ conn .auth_storage = weakref .ref (store )
53+ return store
54+
55+ def __init__ (self , conn ):
56+ self .conn = conn
57+ self .pw = ''
58+ self ._auth_file = ''
59+
60+ @property
61+ def auth_file (self ):
62+ if self ._auth_file is None :
63+ return ''
64+ return self ._auth_file or self .conn .account .derived_auth_file
65+
66+ def use_client_auth_file (self , auth_file ):
67+ if isinstance (auth_file , (str , NoneType )):
68+ self ._auth_file = auth_file
69+ else :
70+ msg = f"Invalid object in { self .__class__ } ._auth_file"
71+ raise RuntimeError (msg )
72+
73+ def store_pw (self ,pw ):
74+ if self .auth_file :
75+ self .set_env_password (pw , filename = self .auth_file )
76+ else :
77+ self .pw = pw
78+
79+ def retrieve_pw (self ):
80+ if self .auth_file :
81+ return self .get_env_password (filename = self .auth_file )
82+ return self .pw
683
784
885def load_plugins (subset = set (), _reload = False ):
@@ -18,9 +95,66 @@ def load_plugins(subset=set(), _reload=False):
1895 return dir_
1996
2097
21- # TODO(#499): X models a class which we could define here as a base for various server or client state machines
22- # as appropriate for the various authentication types.
98+ class REQUEST_IS_MISSING_KEY (Exception ): pass
99+
100+
101+ def throw_if_request_message_is_missing_key ( request , required_keys ):
102+ for key in required_keys :
103+ if not key in request :
104+ raise REQUEST_IS_MISSING_KEY (f"key = { key } " )
105+
106+
107+ def _auth_api_request (conn , data ):
108+ message_body = JSON_Message (data , conn .server_version )
109+ message = iRODSMessage ('RODS_API_REQ' , msg = message_body ,
110+ int_info = api_number ['AUTHENTICATION_APN' ]
111+ )
112+ conn .send (message )
113+ response = conn .recv ()
114+ return response .get_json_encoded_struct ()
115+
116+
117+ __FLOW_COMPLETE__ = "authentication_flow_complete"
118+ __NEXT_OPERATION__ = "next_operation"
119+
120+
121+ CLIENT_GET_REQUEST_RESULT = 'client_get_request_result'
122+ FORCE_PASSWORD_PROMPT = "force_password_prompt"
123+ STORE_PASSWORD_IN_MEMORY = "store_password_in_memory"
124+
125+ class authentication_base :
126+
127+ def __init__ (self , connection , scheme ):
128+ self .conn = connection
129+ self .loggedIn = 0
130+ self .scheme = scheme
131+
132+ def call (self , next_operation , request ):
133+ logging .info ('next operation = %r' , next_operation )
134+ old_func = func = next_operation
135+ while isinstance (func , str ):
136+ old_func , func = (func , getattr (self , func , None ))
137+ func = (func or old_func )
138+ if not func :
139+ raise RuntimeError ("client request contains no callable 'next_operation'" )
140+ resp = func (request )
141+ logging .info ('resp = %r' ,resp )
142+ return resp
143+
144+ def authenticate_client (self , next_operation = "auth_client_start" , initial_request = {}):
145+
146+ to_send = initial_request .copy ()
147+ to_send ["scheme" ] = self .scheme
23148
149+ while True :
150+ resp = self .call (next_operation , to_send )
151+ if self .loggedIn :
152+ break
153+ next_operation = resp .get (__NEXT_OPERATION__ )
154+ if next_operation is None :
155+ raise ClientAuthError ("next_operation key missing; cannot determine next operation" )
156+ if next_operation in (__FLOW_COMPLETE__ ,"" ):
157+ raise ClientAuthError (f"authentication flow stopped without success: scheme = { self .scheme } " )
158+ to_send = resp
24159
25- class X :
26- pass
160+ logging .info ("fully authenticated" )
0 commit comments