Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve TOTP enrollment flow to support to enter TOTP in the same page #141

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import static org.wso2.carbon.identity.application.authenticator.totp.TOTPAuthenticatorConstants.AUTHENTICATION;
import static org.wso2.carbon.identity.application.authenticator.totp.TOTPAuthenticatorConstants.ErrorMessages;
import static org.wso2.carbon.identity.application.authenticator.totp.TOTPAuthenticatorConstants.SECRET_KEY_CLAIM_URL;
import static org.wso2.carbon.identity.application.authenticator.totp.TOTPAuthenticatorConstants.TOTP_ENABLED_CLAIM_URI;
import static org.wso2.carbon.identity.application.authenticator.totp.util.TOTPUtil.getMultiOptionURIQueryParam;
import static org.wso2.carbon.identity.application.authenticator.totp.util.TOTPUtil.getTOTPErrorPage;
import static org.wso2.carbon.identity.application.authenticator.totp.util.TOTPUtil.getTOTPLoginPage;
Expand Down Expand Up @@ -126,8 +129,9 @@ public AuthenticatorFlowStatus process(HttpServletRequest request, HttpServletRe
.isNotEmpty(request.getParameter(TOTPAuthenticatorConstants.ENABLE_TOTP))) {
// if the request comes with MOBILE_NUMBER, it will go through this flow.
initiateAuthenticationRequest(request, response, context);
if (context.getProperty(TOTPAuthenticatorConstants.AUTHENTICATION)
.equals(TOTPAuthenticatorConstants.AUTHENTICATOR_NAME)) {
if (TOTPAuthenticatorConstants.AUTHENTICATOR_NAME.equals(
context.getProperty(TOTPAuthenticatorConstants.AUTHENTICATION))) {

return AuthenticatorFlowStatus.INCOMPLETE;
} else {
return AuthenticatorFlowStatus.SUCCESS_COMPLETED;
Expand Down Expand Up @@ -213,37 +217,12 @@ protected void initiateAuthenticationRequest(HttpServletRequest request,
IdentityErrorMsgContext errorContext = IdentityUtil.getIdentityErrorMsg();
IdentityUtil.clearIdentityErrorMsg();

String errorParam = StringUtils.EMPTY;
if (showAuthFailureReason) {
if (errorContext != null && errorContext.getErrorCode() != null) {
log.debug("Identity error message context is not null.");
String errorCode = errorContext.getErrorCode();
if (errorCode != null) {
String reason = null;
if (errorCode.contains(":")) {
String[] errorCodeWithReason = errorCode.split(":", 2);
errorCode = errorCodeWithReason[0];
if (errorCodeWithReason.length > 1) {
reason = errorCodeWithReason[1];
}
}
//Only adds error code if it is locked error code.
if (errorCode.equals(UserCoreConstants.ErrorCode.USER_IS_LOCKED)) {
Map<String, String> paramMap = new HashMap<>();
paramMap.put(TOTPAuthenticatorConstants.ERROR_CODE, errorCode);
if (StringUtils.isNotBlank(reason)) {
paramMap.put(TOTPAuthenticatorConstants.LOCKED_REASON, reason);
}
errorParam = buildErrorParamString(paramMap);
}
}
}
}
boolean isSecretKeyExistForUser =false;
String errorParam = getErrorParamFromErrorContext(showAuthFailureReason, errorContext);
boolean isSecretKeyExistForUser = false;
// Not required to check the TOTP enable state for the initial login of the federated users.
if (!isInitialFederationAttempt) {
isSecretKeyExistForUser = isSecretKeyExistForUser(UserCoreUtil.addDomainToName(username,
authenticatingUser.getUserStoreDomain()));
isSecretKeyExistForUser = isSecretKeyExistForUser(
UserCoreUtil.addDomainToName(username, authenticatingUser.getUserStoreDomain()));
}
if (isSecretKeyExistForUser) {
if (log.isDebugEnabled()) {
Expand All @@ -258,9 +237,8 @@ protected void initiateAuthenticationRequest(HttpServletRequest request,
// authentication option from TOTP pages.
String multiOptionURI = getMultiOptionURIQueryParam(request);

if (isSecretKeyExistForUser &&
request.getParameter(TOTPAuthenticatorConstants.ENABLE_TOTP) == null) {
//if TOTP is enabled for the user.
if (isSecretKeyExistForUser) {
//if TOTP is enabled for the user. should be redirected to totp login page.
String totpLoginPageUrl;
if (!showAuthFailureReasonOnLoginPage) {
errorParam = StringUtils.EMPTY;
Expand Down Expand Up @@ -294,9 +272,33 @@ protected void initiateAuthenticationRequest(HttpServletRequest request,
if (!showAuthFailureReason) {
errorParam = StringUtils.EMPTY;
}
String totpLoginPageUrl = buildTOTPLoginPageURL(context, username, retryParam,
errorParam, multiOptionURI);
response.sendRedirect(totpLoginPageUrl);
boolean isTOTPEnrollInSinglePageEnabled = TOTPUtil.isTOTPEnrollInSinglePageEnabled();
if (isTOTPEnrollInSinglePageEnabled) {
// Process authentication flow if totpEnrollInSinglePage true.
try {
processAuthenticationResponse(request, response, context);
context.setProperty(AUTHENTICATION, null);
} catch (AuthenticationFailedException ex) {
// Remove TOTP secret value for claim when an error occurred in enrollment flow.
Map<String, String> claims = new HashMap<>();
claims.put(TOTP_ENABLED_CLAIM_URI, String.valueOf(false));
claims.put(SECRET_KEY_CLAIM_URL, StringUtils.EMPTY);
setUserClaimValues(authenticatingUser, claims);

retryParam = "&authFailure=true&authFailureMsg=login.fail.message";
errorContext = IdentityUtil.getIdentityErrorMsg();
IdentityUtil.clearIdentityErrorMsg();
errorParam = getErrorParamFromErrorContext(showAuthFailureReason, errorContext);

TOTPUtil.redirectToEnableTOTPReqPage(request, response, context,
(String) context.getProperty(TOTPAuthenticatorConstants.QR_CODE_CLAIM_URL),
errorParam, retryParam);
}
} else {
String totpLoginPageUrl =
buildTOTPLoginPageURL(context, username, retryParam, errorParam, multiOptionURI);
response.sendRedirect(totpLoginPageUrl);
}
} else {
if (isTOTPEnabledByAdmin) {
//if TOTP is not enabled for the user and admin enforces TOTP.
Expand All @@ -323,23 +325,54 @@ protected void initiateAuthenticationRequest(HttpServletRequest request,
throw new AuthenticationFailedException(
"Error when redirecting the TOTP login response, user : " + username, e);
} catch (TOTPException e) {
throw new AuthenticationFailedException(
"Error when checking TOTP enabled for the user : " + username, e);
throw new AuthenticationFailedException("Error when checking TOTP enabled for the user : " + username, e);
} catch (AuthenticationFailedException e) {
throw new AuthenticationFailedException(
"Authentication failed!. Cannot get the username from first step.", e);
throw new AuthenticationFailedException("Authentication failed!. Cannot get the username from first step.",
e);
} catch (URLBuilderException | URISyntaxException e) {
throw new AuthenticationFailedException("Error while building TOTP page URL.", e);
}
}

private String getErrorParamFromErrorContext(boolean showAuthFailureReason, IdentityErrorMsgContext errorContext) {

String errorParam = StringUtils.EMPTY;
if (showAuthFailureReason) {
if (errorContext != null && errorContext.getErrorCode() != null) {
log.debug("Identity error message context is not null.");
String errorCode = errorContext.getErrorCode();
if (errorCode != null) {
String reason = null;
if (errorCode.contains(":")) {
String[] errorCodeWithReason = errorCode.split(":", 2);
errorCode = errorCodeWithReason[0];
if (errorCodeWithReason.length > 1) {
reason = errorCodeWithReason[1];
}
}
//Only adds error code if it is locked error code.
if (errorCode.equals(UserCoreConstants.ErrorCode.USER_IS_LOCKED)) {
Map<String, String> paramMap = new HashMap<>();
paramMap.put(TOTPAuthenticatorConstants.ERROR_CODE, errorCode);
if (StringUtils.isNotBlank(reason)) {
paramMap.put(TOTPAuthenticatorConstants.LOCKED_REASON, reason);
}
errorParam = buildErrorParamString(paramMap);
}
}
}
}
return errorParam;
}

private String buildTOTPLoginPageURL(AuthenticationContext context, String username, String retryParam,
String errorParam, String multiOptionURI)
throws AuthenticationFailedException, URISyntaxException, URLBuilderException {

String queryString = "t=" + context.getLoginTenantDomain() + "&sessionDataKey=" + context.getContextIdentifier()
+ "&authenticators=" + getName() + "&type=totp" + retryParam + "&username=" + username + "&sp="
+ Encode.forUriComponent(context.getServiceProviderName()) + errorParam + multiOptionURI;
String queryString =
"t=" + context.getLoginTenantDomain() + "&sessionDataKey=" + context.getContextIdentifier() +
"&authenticators=" + getName() + "&type=totp" + retryParam + "&username=" + username + "&sp=" +
Encode.forUriComponent(context.getServiceProviderName()) + errorParam + multiOptionURI;
String loginPage = FrameworkUtils.appendQueryParamsStringToUrl(getTOTPLoginPage(context), queryString);
return buildAbsoluteURL(loginPage);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ private TOTPAuthenticatorConstants() {
public static final String CONF_SHOW_AUTH_FAILURE_REASON_ON_LOGIN_PAGE = "showAuthFailureReasonOnLoginPage";
public static final String ERROR_CODE = "errorCode";
public static final String LOCKED_REASON = "lockedReason";
public static final String TOTP_ENROLL_IN_SINGLE_PAGE = "totpEnrollInSinglePage";

/**
* Enum which contains the error codes and corresponding error messages.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -566,12 +566,36 @@ public static void redirectToEnableTOTPReqPage(HttpServletRequest request, HttpS
AuthenticationContext context, String skey)
throws AuthenticationFailedException {

redirectToEnableTOTPReqPage(request, response, context, skey, null, null);
}

/**
* Redirect the enableTOTP request page.
*
* @param request The HttpServletRequest.
* @param response The HttpServletResponse.
* @param context The AuthenticationContext.
* @param skey QR code claim.
* @param errorParam Error parameters.
* @param retryParam Retry parameters.
* @throws AuthenticationFailedException On error while getting value for enrolUserInAuthenticationFlow.
*/
public static void redirectToEnableTOTPReqPage(HttpServletRequest request, HttpServletResponse response,
AuthenticationContext context, String skey, String errorParam,
String retryParam) throws AuthenticationFailedException {

if (isEnrolUserInAuthenticationFlowEnabled(context)) {
String multiOptionURI = getMultiOptionURIQueryParam(request);
String queryParams = "t=" + context.getLoginTenantDomain() + "&sessionDataKey=" +
context.getContextIdentifier() + "&authenticators=" + TOTPAuthenticatorConstants.AUTHENTICATOR_NAME
+ "&type=totp" + "&sp=" + Encode.forUriComponent(context.getServiceProviderName()) +
"&ske=" + skey + multiOptionURI;
if (StringUtils.isNotBlank(retryParam)) {
queryParams += retryParam;
}
if (StringUtils.isNotBlank(errorParam)) {
queryParams += errorParam;
}
String enableTOTPReqPageUrl =
FrameworkUtils.appendQueryParamsStringToUrl(getEnableTOTPPage(context), queryParams);

Expand Down Expand Up @@ -1043,4 +1067,16 @@ private static Map<String, String> mapFederateClaimsToLocal(ExternalIdPConfig ex
return localClaimValues;
}

/**
* Check whether TOTP enrollment in single page enabled.
*
* @return true If TOTP enrollment in single page enabled. Otherwise, return false.
*/
public static boolean isTOTPEnrollInSinglePageEnabled() {

String isTOTPEnrollInSinglePageEnabled = getTOTPParameters()
.getOrDefault(TOTPAuthenticatorConstants.TOTP_ENROLL_IN_SINGLE_PAGE, String.valueOf(false));
return Boolean.parseBoolean(isTOTPEnrollInSinglePageEnabled);
}

}