@@ -69,9 +69,30 @@ def login(client, username, password):
6969 endpoint = C .URL_LAUNCHPAD
7070 meta = {}
7171
72+ last_login_endpoint = None
73+ login_attempt_count = 0
74+ max_login_attempts = 3
75+
7276 while ('SAMLResponse' not in meta and 'login_hint' not in meta ):
7377 endpoint , meta = get_sso_endpoint_meta (client , endpoint , data = meta )
7478 if 'j_username' in meta :
79+
80+ # If no exceptions catch the error, then execution will end in endless loop.
81+ # To prevent that, we track the number of attempts to the same login endpoint
82+ # and raise an exception after a certain threshold is reached.
83+ if last_login_endpoint == endpoint :
84+ login_attempt_count += 1
85+ if login_attempt_count >= max_login_attempts :
86+ raise exceptions .AuthenticationError (
87+ 'Authentication failed due to invalid credentials. '
88+ 'Please verify your S-User ID and password are correct. '
89+ 'If this error persists with valid credentials, the authentication error format may have changed.'
90+ )
91+ else :
92+ # Reset counter when we encounter a different login endpoint
93+ last_login_endpoint = endpoint
94+ login_attempt_count = 1
95+
7596 meta ['j_username' ] = username
7697 meta ['j_password' ] = password
7798 if 'changePassword' in endpoint :
@@ -123,10 +144,23 @@ def get_sso_endpoint_meta(client, url, **kwargs):
123144 # for non-universal SID. For universal SID, the client will raise 401
124145 # during Gygia auth.
125146 error_message = soup .find ('div' , {'id' : 'globalMessages' })
126- if error_message and 'we could not authenticate you' in error_message .text :
127- res .status_code = 401
128- res .reason = 'Unauthorized'
129- res .raise_for_status ()
147+ if error_message :
148+ error_text = error_message .text .lower ()
149+ # List of potential error messages indicating authentication failure.
150+ # This is not exhaustive and may need updates if SAP changes their error messaging.
151+ auth_error_messages = [
152+ 'could not authenticate you' ,
153+ "couldn't authenticate you" ,
154+ 'authentication failed' ,
155+ 'invalid credentials' ,
156+ 'incorrect username or password'
157+ ]
158+
159+ # Check if any of the known authentication error messages are present in the response.
160+ if any (msg in error_text for msg in auth_error_messages ):
161+ res .status_code = 401
162+ res .reason = 'Unauthorized'
163+ res .raise_for_status ()
130164
131165 form = soup .find ('form' )
132166 if not form :
0 commit comments