By: matt dillenkoffer user 14 Sep 2016 at 12:59 p.m. CDT

5 Responses
matt dillenkoffer gravatar
We have a strange issue that looks like it may be a possible bug in Gluu. In the scenario where a user creates a new user account via our web application the user ends up getting created in ActiveDirectory. The new user gets an email with their username and password and they try to login. The webapp redirects them to Gluu, if cache refresh hasn't synced gluu with the ActiveDirectory you get the "Please use correct username and password" message. Which is fine. But if you keep trying you eventually get successfully authenticated but instead of getting redirected back to our webapp you stay on a gluu page that is all white and just has the footer displayed but way up high at the top of the page because there's no content to push it down. Powered by Gluu. Free and open source access management. The above value is all you see on the webpage and the url has changed to <baseGluuURL>/oxauth/login?cid=297768 Looking through the logs I see this in the oxtrust.log every time this happens: 2016-09-14 10:43:17,876 ERROR [org.gluu.oxtrust.ldap.service.InumService] MySQL database error: No suitable driver found for 2016-09-14 10:43:17,876 ERROR [org.gluu.oxtrust.ldap.service.InumService] java.sql.SQLException: No suitable driver found for The final thing to note is that the authentication was IN DEED SUCCESSFUL because if I open a 2nd tab and navigate back to my webapp's homepage I get redirected to Gluu and back to my webapp and get logged in as the user I was previously trying to authenticate with, without having to enter my username and password a 2nd time.

By Aliaksandr Samuseu staff 14 Sep 2016 at 2:22 p.m. CDT

Aliaksandr Samuseu gravatar
Hi, Matt. Looks like you have somewhat complex setup out there. To try to reproduce it we need to know more of flows that happen in your environment. Do you use any custom auth scripts? If yes, could you share it with all its properties? What SSO protocol your app uses when sending user to Gluu? Best regards, Alex.

By matt dillenkoffer user 14 Sep 2016 at 2:31 p.m. CDT

matt dillenkoffer gravatar
we are using an auth script to lock user accounts on multiple invalid login attempts... attributes: invalid_login_count_attribute : oxCountInvalidLogin login_unlock_time_millis : oxUnlockTimeMillis maximum_invalid_login_attempts : 3 maximum_daily_login_lockouts : 3 daily_lockout_count_attribute : oxCountDailyLockout login_lockout_time_millis_1 : 60000 login_lockout_time_millis_2 : 180000 login_lockout_time_millis_3 : 300000 daily_lock_period_end_millis : oxDailyLockEndMillis exempt_user : gridadmin Script Content: # oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. # Copyright (c) 2016, Gluu # # Author: Yuriy Movchan # # Added timeout functionality - Emily Hennessy from org.jboss.seam.security import Identity from org.jboss.seam.faces import FacesMessages from org.jboss.seam.international import StatusMessage from org.jboss.seam.contexts import Context, Contexts from org.xdi.model.custom.script.type.auth import PersonAuthenticationType from org.xdi.oxauth.service import UserService from org.xdi.service import MailService from org.xdi.util import StringHelper from org.gluu.site.ldap.persistence.exception import AuthenticationException import java from java.lang import System as javasystem class PersonAuthentication(PersonAuthenticationType): def __init__(self, currentTimeMillis): self.currentTimeMillis = currentTimeMillis def init(self, configurationAttributes): print "TimeoutLockAccount. Init" # ox attributes self.invalidLoginCountAttribute = "oxCountInvalidLogin" if configurationAttributes.containsKey("invalid_login_count_attribute"): self.invalidLoginCountAttribute = configurationAttributes.get("invalid_login_count_attribute").getValue2() else: print "TimeoutLockAccount. Init. Using default invalid login count attribute" self.loginUnlockTimeMillis = "oxUnlockTimeMillis" if configurationAttributes.containsKey("login_unlock_time_millis"): self.loginUnlockTimeMillis = configurationAttributes.get("login_unlock_time_millis").getValue2() else: print "TimeoutLockAccount. Init. Using default unlock time attribute" self.dailyLockoutCountAttribute = "oxCountDailyLockout" if configurationAttributes.containsKey("daily_lockout_count_attribute"): self.dailyLockoutCountAttribute = configurationAttributes.get("daily_lockout_count_attribute").getValue2() else: print "TimeoutLockAccount. Init. Using default daily lockout attribute." self.dailyLockPeriodEndMillis = "oxDailyLockEndMillis" if configurationAttributes.containsKey("daily_lock_period_end_millis"): self.dailyLockPeriodEndMillis = configurationAttributes.get("daily_lock_period_end_millis").getValue2() else: print "TimeoutLockAccount. Init. Using default daily lock end period attribute." print "TimeoutLockAccount. Init success. invalid_login_count_attribute: '%s', login_unlock_time_millis: '%s', daily_lockout_count_attribute: '%s', daily_lock_period_end_millis: '%s'" % ( self.invalidLoginCountAttribute, self.loginUnlockTimeMillis, self.dailyLockoutCountAttribute, self.dailyLockPeriodEndMillis) # script custom maximum properties self.maximumInvalidLoginAttempts = 3 if configurationAttributes.containsKey("maximum_invalid_login_attempts"): self.maximumInvalidLoginAttempts = StringHelper.toInteger( configurationAttributes.get("maximum_invalid_login_attempts").getValue2()) else: print "TimeoutLockAccount. Init. Using default number attempts" self.maximumDailyLoginLockouts = 3 if configurationAttributes.containsKey("maximum_daily_login_lockouts"): self.maximumDailyLoginLockouts = StringHelper.toInteger( configurationAttributes.get("maximum_daily_login_lockouts").getValue2()) else: print "TimeoutLockAccount. Init. Using default number lockouts" print "TimeoutLockAccount. Init success. maximum_invalid_login_attempts: '%s', maximum_daily_login_lockouts: '%s'" % ( self.maximumInvalidLoginAttempts, self.maximumDailyLoginLockouts) # script custom lockout time properties self.loginLockoutTimeMillis1 = 60000 if configurationAttributes.containsKey("login_lockout_time_millis_1"): self.loginLockoutTimeMillis1 = StringHelper.toInteger( configurationAttributes.get("login_lockout_time_millis_1").getValue2()) else: print "TimeoutLockAccount. Init. Using default lockout time 1" self.loginLockoutTimeMillis2 = 180000 if configurationAttributes.containsKey("login_lockout_time_millis_2"): self.loginLockoutTimeMillis2 = StringHelper.toInteger( configurationAttributes.get("login_lockout_time_millis_2").getValue2()) else: print "TimeoutLockAccount. Init. Using default lockout time 2" self.loginLockoutTimeMillis3 = 300000 if configurationAttributes.containsKey("login_lockout_time_millis_3"): self.loginLockoutTimeMillis3 = StringHelper.toInteger( configurationAttributes.get("login_lockout_time_millis_3").getValue2()) else: print "TimeoutLockAccount. Init. Using default lockout time 3" print "TimeoutLockAccount. Init success. login_lockout_time_millis_1: '%s', login_lockout_time_millis_2: '%s', login_lockout_time_millis_3: '%s'" % ( self.loginLockoutTimeMillis1, self.loginLockoutTimeMillis2, self.loginLockoutTimeMillis3) # user exempt from lockout self.exemptUser = None if configurationAttributes.containsKey("exempt_user"): self.exemptUser = configurationAttributes.get("exempt_user").getValue2() else: print "TimeoutLockAccount. Init. No exempt users." return True def destroy(self, configurationAttributes): print "TimeoutLockAccount. Destroy" print "TimeoutLockAccount. Destroy success" return True def getApiVersion(self): return 1 def isValidAuthenticationMethod(self, usageType, configurationAttributes): return True def getAlternativeAuthenticationMethod(self, usageType, configurationAttributes): return None # noinspection PyTypeChecker def authenticate(self, configurationAttributes, requestParameters, step): if step == 1: print "TimeoutLockAccount. Authenticate for step 1" credentials = Identity.instance().getCredentials() user_name = credentials.getUsername() user_password = credentials.getPassword() # exempt user? if self.exemptUser != None and self.exemptUser == user_name: return self.login(user_name, user_password) # is user over max daily lockouts? # get lockout count daily_lockout_count = StringHelper.toInteger( self.getUserAttributeValue(user_name, self.dailyLockoutCountAttribute), 0) # check if user perm locked if self.isPermanentlyLockedOut(user_name, daily_lockout_count): print "TimeoutLockAccount. Authenticate. User '%s' is permanently locked out." % user_name return False # is user currently locked out? # get current time & unlock time current_time_millis = javasystem.currentTimeMillis() unlock_time_millis = self.getUserAttributeValue(user_name, self.loginUnlockTimeMillis) # check unlock time for type conversion if unlock_time_millis == None: unlock_time_millis = 0 else: unlock_time_millis = long(unlock_time_millis) # check if user locked if self.isLockedOut(user_name, current_time_millis, unlock_time_millis): return False # was user previously locked out? # get invalid login count invalid_login_count = StringHelper.toInteger(self.getUserAttributeValue(user_name, self.invalidLoginCountAttribute), 0) # check prev locked if unlock_time_millis != 0: invalid_login_count = 0 unlock_time_millis = 0 self.setUserAttributeValue(user_name, self.loginUnlockTimeMillis, StringHelper.toString(0)) self.setUserAttributeValue(user_name, self.invalidLoginCountAttribute, StringHelper.toString(0)) self.toggleUserLock(user_name, False) # finally attempt to login logged_in = self.login(user_name, user_password) # authentication failed, determine status if (not logged_in): # if user hasn't reached lock point if invalid_login_count < self.maximumInvalidLoginAttempts: invalid_login_count += 1 self.setUserAttributeValue(user_name, self.invalidLoginCountAttribute, StringHelper.toString(invalid_login_count)) # user has reached lock point (after 3rd lockout, user only has 1 try) if invalid_login_count >= self.maximumInvalidLoginAttempts or daily_lockout_count == 3: self.handleLockout(user_name, current_time_millis, daily_lockout_count) return False # successful login = reset invalid login attempts self.setUserAttributeValue(user_name, self.invalidLoginCountAttribute, StringHelper.toString(0)) return True else: return False def prepareForStep(self, configurationAttributes, requestParameters, step): if step == 1: print "TimeoutLockAccount. Prepare for Step 1" return True else: return False def getClientId(self): context = Contexts.getEventContext() if context is None: return None client_id = context.get("client_id") if client_id is None: return None return client_id def getExtraParametersForStep(self, configurationAttributes, step): return None def getCountAuthenticationSteps(self, configurationAttributes): return 1 def getPageForStep(self, configurationAttributes, step): if step == 1: client_id = self.getClientId() if (client_id is None): return "/login.xhtml" if (client_id == "@!828C.076E.9EF6.A8B2!0001!D8E0.38DF!0008!1C25.9BCC"): return "/admin-login.xhtml" if (client_id == "@!828C.076E.9EF6.A8B2!0001!D8E0.38DF!0008!2844.345E"): return "/dcxm-login.xhtml" if (client_id == "@!828C.076E.9EF6.A8B2!0001!D8E0.38DF!0008!DE82.6066"): return "/patient-login.xhtml" return "/login.xhtml" else: return "" def logout(self, configurationAttributes, requestParameters): return True # determine if permanently locked out def isPermanentlyLockedOut(self, user_name, daily_lockout_count): if daily_lockout_count > self.maximumDailyLoginLockouts: self.displayLockoutError(True) return True return False def isLockedOut(self, user_name, current_millis, unlock_millis): print "TimeoutLockAccount. IsLockedOut. Comparing current time: '%s' to unlock time: '%s'." % ( str(current_millis), str(unlock_millis)) # check if user currently locked out if unlock_millis != 0 and current_millis <= unlock_millis: print "TimeoutLockAccount. IsLockedOut. User '%s' is currently locked out." % user_name self.displayLockoutError(False) return True return False def login(self, user_name, user_password): login_success = False if (StringHelper.isNotEmptyString(user_name) and StringHelper.isNotEmptyString(user_password)): userService = UserService.instance() try: login_success = userService.authenticate(user_name, user_password) except AuthenticationException: print "TimeoutLockAccount. Login. Failed to authenticate user '%s'" % user_name return login_success def handleLockout(self, user_name, current_millis, lockout_count): perm_lock = False # get end of lockout period & type check period_end_millis = self.getUserAttributeValue(user_name, self.dailyLockPeriodEndMillis) if period_end_millis == None: period_end_millis = 0 else: period_end_millis = long(period_end_millis) # set lock period end on first lockout if (lockout_count == 0): self.setDailyLockPeriodEnd(user_name, current_millis) # check if need to reset lockout elif (lockout_count <= self.maximumDailyLoginLockouts) and self.isDailyLockPeriodExpired( current_millis, period_end_millis): lockout_count = 0 self.setDailyLockPeriodEnd(user_name, current_millis) # increment daily lockout lockout_count += 1 self.setUserAttributeValue(user_name, self.dailyLockoutCountAttribute, StringHelper.toString(lockout_count)) # increment unlock time loginUnlockTimeMillisString = str(self.getUnlockTime(current_millis, lockout_count)) self.setUserAttributeValue(user_name, self.loginUnlockTimeMillis, loginUnlockTimeMillisString) # lock user self.toggleUserLock(user_name, True) if lockout_count > self.maximumDailyLoginLockouts: perm_lock = True self.sendLockoutEmail(user_name) self.displayLockoutError(perm_lock) return # sets the daily lock period to 24 hrs after first lockout def setDailyLockPeriodEnd(self, user_name, current_millis): dailyLockPeriodEndMillisLong = current_millis + 86400000 dailyLockPeriodEndMillisString = str(dailyLockPeriodEndMillisLong) self.setUserAttributeValue(user_name, self.dailyLockPeriodEndMillis, dailyLockPeriodEndMillisString) return # determines if the 24 hr daily lock period has ended def isDailyLockPeriodExpired(self, current_millis, daily_lock_period_end_millis): if current_millis <= daily_lock_period_end_millis: return False return True # returns unlock time based on current time + appropriate lockout time def getUnlockTime(self, current_millis, daily_lockout_count): lockout_time = 0 if daily_lockout_count == 1: lockout_time = self.loginLockoutTimeMillis1 elif daily_lockout_count == 2: lockout_time = self.loginLockoutTimeMillis2 elif daily_lockout_count == 3: lockout_time = self.loginLockoutTimeMillis3 unlockTimeLong = current_millis + long(lockout_time) return unlockTimeLong # sends email to inform user they are permanently locked out def sendLockoutEmail(self, user_name): email_address = self.getUserAttributeValue(user_name, "mail") if email_address == None: print "TimeoutLockAccount. SendEmail. No user email." return print "TimeoutLockAccount. SendEmail. Sending email." mailService = MailService.instance() subject = "Vitality Account Locked" body = "You are permanently locked out of Vitality. Please contact your system administrator to unlock your account." mailService.sendMail(email_address, subject, body) return # display lockout error def displayLockoutError(self, is_perm_lock): error_msg = "You are currently locked out. Please try again later." if is_perm_lock: error_msg = "You are permanently locked out. Please contact your system administrator." facesMessages = FacesMessages.instance() facesMessages.add(StatusMessage.Severity.ERROR, error_msg, None) # get a custom user attribute def getUserAttributeValue(self, user_name, attribute_name): if StringHelper.isEmpty(user_name): return None userService = UserService.instance() find_user_by_uid = userService.getUser(user_name, attribute_name) if find_user_by_uid == None: print "TimeoutLockAccount. GetUserAttributeValue. User '%s' for attribute '%s' not found." % ( user_name, attribute_name) return None custom_attribute_value = userService.getCustomAttribute(find_user_by_uid, attribute_name) if custom_attribute_value == None: return None attribute_value = custom_attribute_value.getValue() print "TimeoutLockAccount. GetUserAttributeValue. User's '%s' attribute '%s' = '%s'" % ( user_name, attribute_name, attribute_value) return attribute_value # set a custom user attribute def setUserAttributeValue(self, user_name, attribute_name, attribute_value): if StringHelper.isEmpty(user_name): return None userService = UserService.instance() find_user_by_uid = userService.getUser(user_name) if find_user_by_uid == None: print "TimeoutLockAccount. SetUserAttributeValue. User '%s' for attribute '%s' not found." % ( user_name, attribute_name) return None userService.setCustomAttribute(find_user_by_uid, attribute_name, attribute_value) updated_user = userService.updateUser(find_user_by_uid) print "TimeoutLockAccount. SetUserAttributeValue. User's '%s' attribute '%s' = '%s'" % ( user_name, attribute_name, attribute_value) return updated_user # changes user status to active or inactive based on it unlocking or locking def toggleUserLock(self, user_name, locking): # determine vars by locking/unlocking actionString = "unlock" statusString = "active" if (locking): actionString = "lock" statusString = "inactive" if StringHelper.isEmpty(user_name): return None userService = UserService.instance() find_user_by_uid = userService.getUser(user_name) if (find_user_by_uid == None): return None status_attribute_value = userService.getCustomAttribute(find_user_by_uid, "gluuStatus") if status_attribute_value != None: user_status = status_attribute_value.getValue() if StringHelper.equals(user_status, statusString): print "TimeoutLockAccount. ToggleUserLock. User '%s' '%s'ed already" % (user_name, actionString) return userService.setCustomAttribute(find_user_by_uid, "gluuStatus", statusString) updated_user = userService.updateUser(find_user_by_uid) print "TimeoutLockAccount. ToggleUserLock. User '%s' '%s'ed" % (user_name, actionString) We are using OpenID Connect as the protocol to redirect from Application to Gluu

By Aliaksandr Samuseu staff 14 Sep 2016 at 2:41 p.m. CDT

Aliaksandr Samuseu gravatar
Thanks. Could you also do a search with `ldapsearch` in the container with filter `(objectclass=gluuappliance)`, and provide us its output (it will contain encrypted passwords and details about your infrastructure, you may need to remove at least the former)? You can use attachment feature of the boards for such huge chunks of code, if it's available to you.

By matt dillenkoffer user 14 Sep 2016 at 3:04 p.m. CDT

matt dillenkoffer gravatar
``` -bash-4.2# ./ldapsearch -h 127.0.0.1 -p 1636 -s sub -T -Z -X -D 'cn=directory manager' -w '********' -b 'o=gluu' -z 5 '(objectclass=gluuappliance)' dn: inum=@!828C.076E.9EF6.A8B2!0002!ABE1.CD7A,ou=appliances,o=gluu objectClass: gluuAppliance objectClass: top gluuMaxLogSize: 200 gluuVdsCacheRefreshLastUpdateCount: 0 gluuLastUpdate: 1473883374 gluuHostname: 10 gluuGroupCount: 1 gluuPersonCount: 1605 oxTrustAuthenticationMode: TimeoutLockAccount gluuDSstatus: true gluuFederationHostingEnabled: disabled gluuSmtpFromName: MedConX Support gluuBandwidthTX: -1 oxAuthenticationMode: TimeoutLockAccount gluuScimEnabled: disabled gluuVdsCacheRefreshEnabled: enabled gluuSmtpHost: 10.7.115.25 gluuOrgProfileMgt: disabled gluuFreeSwap: 100 gluuFreeDiskSpace: 60 gluuBandwidthRX: -1 gluuSmtpFromEmailAddress: MedConX@support.nanthealth.com inum: @!828C.076E.9EF6.A8B2!0002!ABE1.CD7A gluuIpAddress: 10.7.152.29 gluuSslExpiry: 273 oxSmtpConfiguration: {"host":"10.7.115.25","port":25,"requiresSsl":false,"fromName":"MedConX Support","fromEmailAddress":"MedConX@support.nanthealth.com","requiresAuthentication":false,"userName":"","password":null} gluuSmtpPort: 25 gluuWhitePagesEnabled: disabled gluuVdsCacheRefreshProblemCount: 0 oxIDPAuthentication: {"type":"auth","name":"vitl_ldap_server","level":0,"priority":0,"enabled":true,"version":1,"fields":[],"config":"{\"configId\":\"vitl_ldap_server\",\"bindDN\":\"cn=gridadmin,ou=users,dc=trio,dc=giab\",\"bindPassword\":\"XXXXXXXXXXXXX\",\"servers\":[\"10.7.152.112:389\"],\"maxConnections\":10,\"useSSL\":false,\"baseDNs\":[\"ou=users,dc=trio,dc=giab\"],\"primaryKey\":\"uid\",\"localPrimaryKey\":\"uid\",\"useAnonymousBind\":false,\"enabled\":true,\"version\":0}"} passwordResetAllowed: disabled oxTrustStoreConf: {"useJreCertificates":true} gluuSmtpRequiresAuthentication: false gluuFreeMemory: 62 gluuSystemUptime: 11030357 gluuSmtpRequiresSsl: false gluuHTTPstatus: false gluuVdsCacheRefreshLastUpdate: 1473883380 gluuVdsCacheRefreshPollingInterval: 1 gluuManageIdentityPermission: enabled oxLogViewerConfig: { "log_template":[ { "value1":"oxAuth logs", "value2":"/opt/tomcat/logs/oxauth.log*", "description":"" }, { "value1":"oxTrust logs", "value2":"/opt/tomcat/logs/oxtrust.log*", "description":"" }, { "value1":"oxTrust cache refresh logs", "value2":"/opt/tomcat/logs/oxtrust_cache_refresh.log*", "description":"" }, { "value1":"console log", "value2":"/opt/tomcat/logs/wrapper.log*", "description":"" } ]} oxTrustCacheRefreshServerIpAddress: 10.7.152.29 ```

By matt dillenkoffer user 16 Sep 2016 at 9:59 a.m. CDT

matt dillenkoffer gravatar
Another interesting thing to note. We disabled our auth script and made sure the Authentication and oxTrust mode were both set back to default and we still get the same exact behavior with the white screen on the first login attempt for a new user that just came in during cache refresh.