Skip to content

Commit

Permalink
Improve authentication api error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
janakamarasena committed Nov 10, 2023
1 parent 28de205 commit 81a761c
Show file tree
Hide file tree
Showing 8 changed files with 347 additions and 147 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@
import org.wso2.carbon.identity.application.authentication.framework.model.auth.service.AuthServiceErrorInfo;
import org.wso2.carbon.identity.application.authentication.framework.model.auth.service.AuthServiceRequest;
import org.wso2.carbon.identity.application.authentication.framework.model.auth.service.AuthServiceResponse;
import org.wso2.carbon.identity.oauth.common.OAuth2ErrorCodes;
import org.wso2.carbon.identity.application.authentication.framework.util.auth.service.AuthServiceConstants;
import org.wso2.carbon.identity.oauth.common.OAuthConstants;
import org.wso2.carbon.identity.oauth.endpoint.OAuthRequestWrapper;
import org.wso2.carbon.identity.oauth.endpoint.api.auth.model.APIError;
import org.wso2.carbon.identity.oauth.endpoint.api.auth.model.AuthRequest;
import org.wso2.carbon.identity.oauth.endpoint.api.auth.model.AuthResponse;
import org.wso2.carbon.identity.oauth.endpoint.authz.OAuth2AuthzEndpoint;
Expand Down Expand Up @@ -87,21 +88,24 @@ public Response handleAuthentication(@Context HttpServletRequest request, @Conte
case SUCCESS_COMPLETED:
return handleSuccessCompletedAuthResponse(request, response, authServiceResponse);
case FAIL_INCOMPLETE:
return handleFailAuthResponse(authServiceResponse);
return handleFailIncompleteAuthResponse(authServiceResponse);
case FAIL_COMPLETED:
return handleFailAuthResponse(authServiceResponse);
return handleFailCompletedAuthResponse(authServiceResponse);
default:
throw new AuthServiceException("Unknown flow status: " + authServiceResponse.getFlowStatus());
throw new AuthServiceException(
AuthServiceConstants.ErrorMessage.ERROR_UNKNOWN_AUTH_FLOW_STATUS.code(),
String.format(AuthServiceConstants.ErrorMessage.ERROR_UNKNOWN_AUTH_FLOW_STATUS
.description(), authServiceResponse.getFlowStatus()));
}

} catch (AuthServiceClientException | InvalidRequestParentException e) {
} catch (AuthServiceClientException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Client error while handling authentication request.", e);
}
return buildOAuthInvalidRequestError(e.getMessage());
} catch (AuthServiceException | URISyntaxException e) {
return ApiAuthnUtils.buildResponseForClientError(e);
} catch (AuthServiceException e) {
LOG.error("Error while handling authentication request.", e);
return buildOAuthServerError();
return ApiAuthnUtils.buildResponseForServerError(e);
}
}

Expand All @@ -112,19 +116,21 @@ private AuthRequest buildAuthRequest(String payload) throws AuthServiceClientExc
return objectMapper.readValue(payload, AuthRequest.class);
} catch (JsonProcessingException e) {
// Throwing a client exception here as the exception can occur due to a malformed request.
throw new AuthServiceClientException(e.getMessage());
throw new AuthServiceClientException(AuthServiceConstants.ErrorMessage.ERROR_INVALID_AUTH_REQUEST.code(),
"Error occurred while parsing the authentication request.", e);
}
}

private Response buildResponse(AuthResponse response) {
private Response buildResponse(AuthResponse response) throws AuthServiceException {

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
String jsonString = null;
String jsonString;
try {
jsonString = objectMapper.writeValueAsString(response);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
throw new AuthServiceException(AuthServiceConstants.ErrorMessage.ERROR_UNABLE_TO_PROCEED.code(),
"Error while building JSON response.", e);
}
return Response.ok().entity(jsonString).build();
}
Expand All @@ -144,10 +150,15 @@ private AuthServiceRequest getAuthServiceRequest(HttpServletRequest request, Htt
params.put(AUTHENTICATOR, new String[]{authenticatorIdSplit[0]});
params.put(IDP, new String[]{authenticatorIdSplit[1]});
} else {
throw new AuthServiceClientException("Provided authenticator id: " + authenticatorId + " is invalid.");
throw new AuthServiceClientException(
AuthServiceConstants.ErrorMessage.ERROR_INVALID_AUTHENTICATOR_ID.code(),
String.format(AuthServiceConstants.ErrorMessage.ERROR_INVALID_AUTHENTICATOR_ID.description(),
authenticatorId));
}
} else {
throw new AuthServiceClientException("Authenticator id is not provided.");
throw new AuthServiceClientException(
AuthServiceConstants.ErrorMessage.ERROR_INVALID_AUTHENTICATOR_ID.code(),
"Authenticator id is not provided.");
}

Map<String, String[]> authParams = authRequest.getSelectedAuthenticator().getParams().entrySet().stream()
Expand All @@ -165,8 +176,8 @@ private String base64URLDecode(String value) {
}

private Response handleSuccessCompletedAuthResponse(HttpServletRequest request, HttpServletResponse response,
AuthServiceResponse authServiceResponse) throws
InvalidRequestParentException, URISyntaxException {
AuthServiceResponse authServiceResponse)
throws AuthServiceException {

String callerSessionDataKey = authServiceResponse.getSessionDataKey();

Expand All @@ -175,7 +186,12 @@ private Response handleSuccessCompletedAuthResponse(HttpServletRequest request,
OAuthRequestWrapper internalRequest = new OAuthRequestWrapper(request, internalParamsList);
internalRequest.setInternalRequest(true);

return oAuth2AuthzEndpoint.authorize(internalRequest, response);
try {
return oAuth2AuthzEndpoint.authorize(internalRequest, response);
} catch (InvalidRequestParentException | URISyntaxException e) {
throw new AuthServiceException(AuthServiceConstants.ErrorMessage.ERROR_INVALID_AUTH_REQUEST.code(),
"Error while processing the final oauth authorization request.", e);
}
}

private Response handleIncompleteAuthResponse(AuthServiceResponse authServiceResponse) throws AuthServiceException {
Expand All @@ -184,35 +200,42 @@ private Response handleIncompleteAuthResponse(AuthServiceResponse authServiceRes
return buildResponse(authResponse);
}

private Response handleFailAuthResponse(AuthServiceResponse authServiceResponse) {
private Response handleFailIncompleteAuthResponse(AuthServiceResponse authServiceResponse)
throws AuthServiceException {

String errorMsg = "Unhandled flow status: " + authServiceResponse.getFlowStatus();
if (authServiceResponse.getErrorInfo().isPresent()) {
AuthServiceErrorInfo errorInfo = authServiceResponse.getErrorInfo().get();
errorMsg = errorInfo.getErrorCode() + " - " + errorInfo.getErrorMessage();
}
Map<String, String> params = new HashMap<>();
params.put(OAuthConstants.OAUTH_ERROR, OAuth2ErrorCodes.SERVER_ERROR);
params.put(OAuthConstants.OAUTH_ERROR_DESCRIPTION, errorMsg);
String jsonString = new Gson().toJson(params);
return Response.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR).entity(jsonString).build();
AuthResponse authResponse = API_AUTHN_HANDLER.handleResponse(authServiceResponse);
return buildResponse(authResponse);
}

private Response buildOAuthInvalidRequestError(String errorMessage) {
private Response handleFailCompletedAuthResponse(AuthServiceResponse authServiceResponse) {

Map<String, String> params = new HashMap<>();
params.put(OAuthConstants.OAUTH_ERROR, OAuth2ErrorCodes.INVALID_REQUEST);
params.put(OAuthConstants.OAUTH_ERROR_DESCRIPTION, errorMessage);
String jsonString = new Gson().toJson(params);
return Response.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR).entity(jsonString).build();
APIError apiError = new APIError();
if (authServiceResponse.getErrorInfo().isPresent()) {
AuthServiceErrorInfo errorInfo = authServiceResponse.getErrorInfo().get();
apiError.setCode(errorInfo.getErrorCode());
apiError.setMessage(errorInfo.getErrorMessage());
apiError.setDescription(errorInfo.getErrorDescription());
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Error info is not present in the authentication service response. " +
"Setting default error details.");
}
apiError.setCode(getDefaultAuthenticationFailureError().code());
apiError.setMessage(getDefaultAuthenticationFailureError().message());
apiError.setDescription(getDefaultAuthenticationFailureError().description());
}
apiError.setTraceId(ApiAuthnUtils.getCorrelationId());
String jsonString = new Gson().toJson(apiError);
/* Authentication FAIL_COMPLETED status could happen due to both client errors and server errors.
Generally FAIL_COMPLETED status is received when an authenticator throws a AuthenticationFailedException
and this exception could be thrown for both client and server errors and with the current framework
implementation it is not possible to distinguish between these two types of errors. Therefore,
it was decided to set the http status code to 400.*/
return Response.status(HttpServletResponse.SC_BAD_REQUEST).entity(jsonString).build();
}

private Response buildOAuthServerError() {
private AuthServiceConstants.ErrorMessage getDefaultAuthenticationFailureError() {

Map<String, String> params = new HashMap<>();
params.put(OAuthConstants.OAUTH_ERROR, OAuth2ErrorCodes.SERVER_ERROR);
params.put(OAuthConstants.OAUTH_ERROR_DESCRIPTION, "Server error occurred while performing authentication.");
String jsonString = new Gson().toJson(params);
return Response.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR).entity(jsonString).build();
return AuthServiceConstants.ErrorMessage.ERROR_AUTHENTICATION_FAILURE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@
import org.wso2.carbon.identity.oauth.endpoint.api.auth.model.AuthResponse;
import org.wso2.carbon.identity.oauth.endpoint.api.auth.model.Authenticator;
import org.wso2.carbon.identity.oauth.endpoint.api.auth.model.AuthenticatorMetadata;
import org.wso2.carbon.identity.oauth.endpoint.api.auth.model.FlowStatusEnum;
import org.wso2.carbon.identity.oauth.endpoint.api.auth.model.Link;
import org.wso2.carbon.identity.oauth.endpoint.api.auth.model.Message;
import org.wso2.carbon.identity.oauth.endpoint.api.auth.model.MessageTypeEnum;
import org.wso2.carbon.identity.oauth.endpoint.api.auth.model.NextStep;
import org.wso2.carbon.identity.oauth.endpoint.api.auth.model.Param;
import org.wso2.carbon.identity.oauth.endpoint.api.auth.model.StepTypeEnum;
Expand All @@ -60,6 +60,7 @@ public class ApiAuthnHandler {
private static final String AUTHENTICATION_EP_LINK_NAME = "authentication";
private static final String TENANT_CONTEXT_PATH_COMPONENT = "/t/%s";
private static final String HTTP_POST = "POST";
private static final String MESSAGE = "message";

/**
* Build the response for the authentication API.
Expand All @@ -72,7 +73,7 @@ public AuthResponse handleResponse(AuthServiceResponse authServiceResponse) thro

AuthResponse authResponse = new AuthResponse();
authResponse.setFlowId(authServiceResponse.getSessionDataKey());
authResponse.setFlowStatus(getFlowStatus(authServiceResponse.getFlowStatus()));
authResponse.setFlowStatus(authServiceResponse.getFlowStatus());
NextStep nextStep = buildNextStep(authServiceResponse);
authResponse.setNextStep(nextStep);
authResponse.setLinks(buildLinks());
Expand Down Expand Up @@ -116,6 +117,14 @@ private Authenticator buildAuthenticatorData(AuthenticatorData authenticatorData
private List<Message> buildMessages(AuthServiceResponse authServiceResponse) {

List<Message> messages = new ArrayList<>();
if (authServiceResponse.getErrorInfo().isPresent()) {
Message errorMessage = new Message();
errorMessage.setType(MessageTypeEnum.ERROR);
errorMessage.setMessageId(authServiceResponse.getErrorInfo().get().getErrorCode());
errorMessage.setMessage(authServiceResponse.getErrorInfo().get().getErrorMessage());
errorMessage.setI18nKey(MESSAGE + authServiceResponse.getErrorInfo().get().getErrorCode());
messages.add(errorMessage);
}
return messages;
}

Expand Down Expand Up @@ -178,7 +187,8 @@ private List<Link> buildLinks() throws AuthServiceException {
try {
href = ServiceURLBuilder.create().addPath(endpoint).build().getAbsolutePublicURL();
} catch (URLBuilderException e) {
throw new AuthServiceException("Error occurred while building links", e);
throw new AuthServiceException(AuthServiceConstants.ErrorMessage.ERROR_UNABLE_TO_PROCEED.code(),
"Error occurred while building links", e);
}
authnEpLink.setHref(href);
authnEpLink.setMethod(HTTP_POST);
Expand All @@ -200,24 +210,6 @@ private String buildAuthenticatorId(String authenticator, String idp) {
return base64URLEncode(authenticator + OAuthConstants.AUTHENTICATOR_IDP_SPLITTER + idp);
}

private FlowStatusEnum getFlowStatus(AuthServiceConstants.FlowStatus flowStatus) throws
AuthServiceException {

switch (flowStatus) {
case INCOMPLETE:
return FlowStatusEnum.INCOMPLETE;
case FAIL_INCOMPLETE:
return FlowStatusEnum.FAIL_INCOMPLETE;
case SUCCESS_COMPLETED:
return FlowStatusEnum.SUCCESS_COMPLETED;
case FAIL_COMPLETED:
return FlowStatusEnum.FAIL_COMPLETED;
default:
throw new AuthServiceException("Unknown flow status: " + flowStatus +
"received from the Authentication Service.");
}
}

private StepTypeEnum getStepType(boolean isMultiOps) {

if (isMultiOps) {
Expand Down
Loading

0 comments on commit 81a761c

Please sign in to comment.