Hi Aliaksandr,
Here is a more complete version of the trace (the preceding/following entries have a 2 minute time gap):
```
2019-06-11 07:01:04,865 ERROR [qtp804611486-115401] [org.xdi.oxauth.auth.Authenticator] (Authenticator.java:608) - Failed to get attributes from session
2019-06-11 07:01:14,927 INFO [qtp804611486-115363] [org.xdi.oxauth.service.AuthenticationService] (AuthenticationService.java:533) - Attempting to redirect user: SessionUser: SessionState {dn='oxAuthSessionId=ebfd4dad-...-8c02299eea12,ou=session,o=@!6D74.52B0.0D98...0CFB,o=gluu', id='ebfd4dad-...-8c02299eea12', lastUsedAt=Tue Jun 11 07:01:14 UTC 2019, userDn='inum=@!6D74.52B0.0D98...3244.0241.D1B3,ou=people,o=@!6D74.52B0.0D98...0CFB,o=gluu', authenticationTime=Tue Jun 11 07:01:14 UTC 2019, state=authenticated, sessionState='02cb...ab5e.0fdb700e-...-f35ffdf38b02', permissionGranted=null, isJwt=false, jwt=null, permissionGrantedMap=org.xdi.oxauth.model.common.SessionIdAccessMap@10b988f5, involvedClients=null, sessionAttributes={session_custom_state=approved, auth_external_attributes=null, opbs=b8aea6fc-...-b74f34a76ede, response_type=code, oxpush2_u2f_device_enroll=false, oxpush2_u2f_device_id=15...56, nonce=5193b337-...-050a288b951d, client_id=@!6D74.52B0.0D98...EDE0, auth_step_passed_1=true, auth_step=2, acr=u2f_opt, remote_ip=1.0.0.1, 10.4.4.4, auth_user=jan, scope=openid profile email user_name, acr_values=u2f_opt, oxpush2_u2f_device_user_inum=@!6D74.52B0.0D98...3244.0241.D1B3, redirect_uri=https://idp...com/identity/authentication/getauthcode, state=ccf8237b-...-c16fe80f14b8, oxpush2_u2f_device_one_step=false}, persisted=true}
2019-06-11 07:01:14,930 INFO [qtp804611486-115363] [org.xdi.oxauth.service.AuthenticationService] (AuthenticationService.java:541) - Attempting to redirect user: User: org.xdi.oxauth.model.common.User@368b2e08
2019-06-11 07:01:14,931 INFO [qtp804611486-115363] [org.xdi.oxauth.auth.Authenticator] (Authenticator.java:409) - Authentication success for User: 'null'
2019-06-11 07:01:15,085 INFO [qtp804611486-115448] [org.xdi.oxauth.auth.Authenticator] (Authenticator.java:262) - Authentication success for Client: '@!6D74.52B0.0D98...EDE0'
```
The default authentication methods are both set to `u2f_opt` (both `u2f_opt` and `u2f` are Enabled under Manage Custom Scripts) - and it has been this way since mid-May. Until then (from mid-June to mid-May) we were only using the default `u2f` for the authentication methods:
```
# oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text.
# Copyright (c) 2016, Gluu
#
# Author: Yuriy Movchan
#
import java
import sys
from javax.ws.rs.core import Response
from org.jboss.resteasy.client import ClientResponseFailure
from org.jboss.resteasy.client.exception import ResteasyClientException
from org.xdi.model.custom.script.type.auth import PersonAuthenticationType
from org.xdi.oxauth.client.fido.u2f import FidoU2fClientFactory
from org.xdi.oxauth.model.config import Constants
from org.xdi.oxauth.security import Identity
from org.xdi.oxauth.service import UserService, AuthenticationService, SessionIdService
from org.xdi.oxauth.service.fido.u2f import DeviceRegistrationService
from org.xdi.oxauth.util import ServerUtil
from org.xdi.service.cdi.util import CdiUtil
from org.xdi.util import StringHelper
class PersonAuthentication(PersonAuthenticationType):
def __init__(self, currentTimeMillis):
self.currentTimeMillis = currentTimeMillis
def init(self, configurationAttributes):
print "U2F. Initialization"
print "U2F. Initialization. Downloading U2F metadata"
u2f_server_uri = configurationAttributes.get("u2f_server_uri").getValue2()
u2f_server_metadata_uri = u2f_server_uri + "/.well-known/fido-u2f-configuration"
metaDataConfigurationService = FidoU2fClientFactory.instance().createMetaDataConfigurationService(u2f_server_metadata_uri)
max_attempts = 20
for attempt in range(1, max_attempts + 1):
try:
self.metaDataConfiguration = metaDataConfigurationService.getMetadataConfiguration()
break
except ClientResponseFailure, ex:
# Detect if last try or we still get Service Unavailable HTTP error
if (attempt == max_attempts) or (ex.getResponse().getResponseStatus() != Response.Status.SERVICE_UNAVAILABLE):
raise ex
java.lang.Thread.sleep(3000)
print "Attempting to load metadata: %d" % attempt
except ResteasyClientException, ex:
# Detect if last try or we still get Service Unavailable HTTP error
if attempt == max_attempts:
raise ex
java.lang.Thread.sleep(3000)
print "Attempting to load metadata: %d" % attempt
print "U2F. Initialized successfully"
return True
def destroy(self, configurationAttributes):
print "U2F. Destroy"
print "U2F. Destroyed successfully"
return True
def getApiVersion(self):
return 1
def isValidAuthenticationMethod(self, usageType, configurationAttributes):
return True
def getAlternativeAuthenticationMethod(self, usageType, configurationAttributes):
return None
def authenticate(self, configurationAttributes, requestParameters, step):
authenticationService = CdiUtil.bean(AuthenticationService)
identity = CdiUtil.bean(Identity)
credentials = identity.getCredentials()
user_name = credentials.getUsername()
if (step == 1):
print "U2F. Authenticate for step 1"
user_password = credentials.getPassword()
logged_in = False
if (StringHelper.isNotEmptyString(user_name) and StringHelper.isNotEmptyString(user_password)):
userService = CdiUtil.bean(UserService)
logged_in = authenticationService.authenticate(user_name, user_password)
if (not logged_in):
return False
return True
elif (step == 2):
print "U2F. Authenticate for step 2"
token_response = ServerUtil.getFirstValue(requestParameters, "tokenResponse")
if token_response == None:
print "U2F. Authenticate for step 2. tokenResponse is empty"
return False
auth_method = ServerUtil.getFirstValue(requestParameters, "authMethod")
if auth_method == None:
print "U2F. Authenticate for step 2. authMethod is empty"
return False
authenticationService = CdiUtil.bean(AuthenticationService)
user = authenticationService.getAuthenticatedUser()
if (user == None):
print "U2F. Prepare for step 2. Failed to determine user name"
return False
if (auth_method == 'authenticate'):
print "U2F. Prepare for step 2. Call FIDO U2F in order to finish authentication workflow"
authenticationRequestService = FidoU2fClientFactory.instance().createAuthenticationRequestService(self.metaDataConfiguration)
authenticationStatus = authenticationRequestService.finishAuthentication(user.getUserId(), token_response)
if (authenticationStatus.getStatus() != Constants.RESULT_SUCCESS):
print "U2F. Authenticate for step 2. Get invalid authentication status from FIDO U2F server"
return False
return True
elif (auth_method == 'enroll'):
print "U2F. Prepare for step 2. Call FIDO U2F in order to finish registration workflow"
registrationRequestService = FidoU2fClientFactory.instance().createRegistrationRequestService(self.metaDataConfiguration)
registrationStatus = registrationRequestService.finishRegistration(user.getUserId(), token_response)
if (registrationStatus.getStatus() != Constants.RESULT_SUCCESS):
print "U2F. Authenticate for step 2. Get invalid registration status from FIDO U2F server"
return False
return True
else:
print "U2F. Prepare for step 2. Authenticatiod method is invalid"
return False
return False
else:
return False
def prepareForStep(self, configurationAttributes, requestParameters, step):
identity = CdiUtil.bean(Identity)
if (step == 1):
return True
elif (step == 2):
print "U2F. Prepare for step 2"
session_id = CdiUtil.bean(SessionIdService).getSessionIdFromCookie()
if StringHelper.isEmpty(session_id):
print "U2F. Prepare for step 2. Failed to determine session_id"
return False
authenticationService = CdiUtil.bean(AuthenticationService)
user = authenticationService.getAuthenticatedUser()
if (user == None):
print "U2F. Prepare for step 2. Failed to determine user name"
return False
u2f_application_id = configurationAttributes.get("u2f_application_id").getValue2()
# Check if user have registered devices
deviceRegistrationService = CdiUtil.bean(DeviceRegistrationService)
userInum = user.getAttribute("inum")
registrationRequest = None
authenticationRequest = None
deviceRegistrations = deviceRegistrationService.findUserDeviceRegistrations(userInum, u2f_application_id)
if (deviceRegistrations.size() > 0):
print "U2F. Prepare for step 2. Call FIDO U2F in order to start authentication workflow"
try:
authenticationRequestService = FidoU2fClientFactory.instance().createAuthenticationRequestService(self.metaDataConfiguration)
authenticationRequest = authenticationRequestService.startAuthentication(user.getUserId(), None, u2f_application_id, session_id)
except ClientResponseFailure, ex:
if (ex.getResponse().getResponseStatus() != Response.Status.NOT_FOUND):
print "U2F. Prepare for step 2. Failed to start authentication workflow. Exception:", sys.exc_info()[1]
return False
else:
print "U2F. Prepare for step 2. Call FIDO U2F in order to start registration workflow"
registrationRequestService = FidoU2fClientFactory.instance().createRegistrationRequestService(self.metaDataConfiguration)
registrationRequest = registrationRequestService.startRegistration(user.getUserId(), u2f_application_id, session_id)
identity.setWorkingParameter("fido_u2f_authentication_request", ServerUtil.asJson(authenticationRequest))
identity.setWorkingParameter("fido_u2f_registration_request", ServerUtil.asJson(registrationRequest))
return True
elif (step == 3):
print "U2F. Prepare for step 3"
return True
else:
return False
def getExtraParametersForStep(self, configurationAttributes, step):
return None
def getCountAuthenticationSteps(self, configurationAttributes):
return 2
def getPageForStep(self, configurationAttributes, step):
if (step == 2):
return "/auth/u2f/login.xhtml"
return ""
def logout(self, configurationAttributes, requestParameters):
return True
```
One thing I didn't know when pasting in the `u2f_opt` code yesterday is that it had already been slightly modified by a colleague as he tried to prevent the issue from re-occurring.
The original `u2f_opt` code that was in place since mid-June up until the issue occurred yesterday morning is:
```
# oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text.
# Copyright (c) 2016, Gluu
#
# Author: Yuriy Movchan
#
import java
import sys
from javax.ws.rs.core import Response
from org.jboss.resteasy.client import ClientResponseFailure
from org.jboss.resteasy.client.exception import ResteasyClientException
from org.xdi.model.custom.script.type.auth import PersonAuthenticationType
from org.xdi.oxauth.client.fido.u2f import FidoU2fClientFactory
from org.xdi.oxauth.model.config import Constants
from org.xdi.oxauth.security import Identity
from org.xdi.oxauth.service import UserService, AuthenticationService, SessionIdService
from org.xdi.oxauth.service.fido.u2f import DeviceRegistrationService
from org.xdi.oxauth.util import ServerUtil
from org.xdi.service.cdi.util import CdiUtil
from org.xdi.util import StringHelper
class PersonAuthentication(PersonAuthenticationType):
def __init__(self, currentTimeMillis):
self.currentTimeMillis = currentTimeMillis
def checkNo2FA(self, user):
roles = user.getAttribute("role", True)
if roles is not None:
if isinstance(roles, unicode):
if roles == "no2fa":
return True
else:
for i in range(roles.length()):
if roles.getString(i) == "no2fa":
return True
return False
def init(self, configurationAttributes):
print "U2F. Initialization"
print "U2F. Initialization. Downloading U2F metadata"
u2f_server_uri = configurationAttributes.get("u2f_server_uri").getValue2()
u2f_server_metadata_uri = u2f_server_uri + "/.well-known/fido-u2f-configuration"
metaDataConfigurationService = FidoU2fClientFactory.instance().createMetaDataConfigurationService(
u2f_server_metadata_uri)
max_attempts = 20
for attempt in range(1, max_attempts + 1):
try:
self.metaDataConfiguration = metaDataConfigurationService.getMetadataConfiguration()
break
except ClientResponseFailure, ex:
# Detect if last try or we still get Service Unavailable HTTP error
if (attempt == max_attempts) or (
ex.getResponse().getResponseStatus() != Response.Status.SERVICE_UNAVAILABLE):
raise ex
java.lang.Thread.sleep(3000)
print "Attempting to load metadata: %d" % attempt
except ResteasyClientException, ex:
# Detect if last try or we still get Service Unavailable HTTP error
if attempt == max_attempts:
raise ex
java.lang.Thread.sleep(3000)
print "Attempting to load metadata: %d" % attempt
print "U2F. Initialized successfully"
return True
def destroy(self, configurationAttributes):
print "U2F. Destroy"
print "U2F. Destroyed successfully"
return True
def getApiVersion(self):
return 1
def isValidAuthenticationMethod(self, usageType, configurationAttributes):
return True
def getAlternativeAuthenticationMethod(self, usageType, configurationAttributes):
return None
def authenticate(self, configurationAttributes, requestParameters, step):
authenticationService = CdiUtil.bean(AuthenticationService)
identity = CdiUtil.bean(Identity)
credentials = identity.getCredentials()
user_name = credentials.getUsername()
if (step == 1):
print "U2F. Authenticate for step 1"
user_password = credentials.getPassword()
logged_in = False
if (StringHelper.isNotEmptyString(user_name) and StringHelper.isNotEmptyString(user_password)):
userService = CdiUtil.bean(UserService)
logged_in = authenticationService.authenticate(user_name, user_password)
if (not logged_in):
return False
if self.checkNo2FA(authenticationService.getAuthenticatedUser()):
identity.setWorkingParameter("no2fa", True)
return True
elif (step == 2):
print "U2F. Authenticate for step 2"
token_response = ServerUtil.getFirstValue(requestParameters, "tokenResponse")
if token_response == None:
print "U2F. Authenticate for step 2. tokenResponse is empty"
return False
auth_method = ServerUtil.getFirstValue(requestParameters, "authMethod")
if auth_method == None:
print "U2F. Authenticate for step 2. authMethod is empty"
return False
authenticationService = CdiUtil.bean(AuthenticationService)
user = authenticationService.getAuthenticatedUser()
if (user == None):
print "U2F. Prepare for step 2. Failed to determine user name"
return False
if (auth_method == 'authenticate'):
print "U2F. Prepare for step 2. Call FIDO U2F in order to finish authentication workflow"
authenticationRequestService = FidoU2fClientFactory.instance().createAuthenticationRequestService(
self.metaDataConfiguration)
authenticationStatus = authenticationRequestService.finishAuthentication(user.getUserId(),
token_response)
if (authenticationStatus.getStatus() != Constants.RESULT_SUCCESS):
print "U2F. Authenticate for step 2. Get invalid authentication status from FIDO U2F server"
return False
return True
elif (auth_method == 'enroll'):
print "U2F. Prepare for step 2. Call FIDO U2F in order to finish registration workflow"
registrationRequestService = FidoU2fClientFactory.instance().createRegistrationRequestService(
self.metaDataConfiguration)
registrationStatus = registrationRequestService.finishRegistration(user.getUserId(), token_response)
if (registrationStatus.getStatus() != Constants.RESULT_SUCCESS):
print "U2F. Authenticate for step 2. Get invalid registration status from FIDO U2F server"
return False
return True
else:
print "U2F. Prepare for step 2. Authenticatiod method is invalid"
return False
return False
else:
return False
def prepareForStep(self, configurationAttributes, requestParameters, step):
identity = CdiUtil.bean(Identity)
if (step == 1):
return True
elif (step == 2):
print "U2F. Prepare for step 2"
session_id = CdiUtil.bean(SessionIdService).getSessionIdFromCookie()
if StringHelper.isEmpty(session_id):
print "U2F. Prepare for step 2. Failed to determine session_id"
return False
authenticationService = CdiUtil.bean(AuthenticationService)
user = authenticationService.getAuthenticatedUser()
if (user == None):
print "U2F. Prepare for step 2. Failed to determine user name"
return False
u2f_application_id = configurationAttributes.get("u2f_application_id").getValue2()
# Check if user have registered devices
deviceRegistrationService = CdiUtil.bean(DeviceRegistrationService)
userInum = user.getAttribute("inum")
registrationRequest = None
authenticationRequest = None
deviceRegistrations = deviceRegistrationService.findUserDeviceRegistrations(userInum, u2f_application_id)
if (deviceRegistrations.size() > 0):
print "U2F. Prepare for step 2. Call FIDO U2F in order to start authentication workflow"
try:
authenticationRequestService = FidoU2fClientFactory.instance().createAuthenticationRequestService(
self.metaDataConfiguration)
authenticationRequest = authenticationRequestService.startAuthentication(user.getUserId(), None,
u2f_application_id,
session_id)
except ClientResponseFailure, ex:
if (ex.getResponse().getResponseStatus() != Response.Status.NOT_FOUND):
print "U2F. Prepare for step 2. Failed to start authentication workflow. Exception:", \
sys.exc_info()[1]
return False
else:
print "U2F. Prepare for step 2. Call FIDO U2F in order to start registration workflow"
registrationRequestService = FidoU2fClientFactory.instance().createRegistrationRequestService(
self.metaDataConfiguration)
registrationRequest = registrationRequestService.startRegistration(user.getUserId(), u2f_application_id,
session_id)
identity.setWorkingParameter("fido_u2f_authentication_request", ServerUtil.asJson(authenticationRequest))
identity.setWorkingParameter("fido_u2f_registration_request", ServerUtil.asJson(registrationRequest))
return True
elif (step == 3):
print "U2F. Prepare for step 3"
return True
else:
return False
def getExtraParametersForStep(self, configurationAttributes, step):
return None
def getCountAuthenticationSteps(self, configurationAttributes):
identity = CdiUtil.bean(Identity)
if identity.isSetWorkingParameter("no2fa"):
return 1
return 2
def getPageForStep(self, configurationAttributes, step):
if (step == 2):
return "/auth/u2f/login.xhtml"
return ""
def logout(self, configurationAttributes, requestParameters):
return True
```