From 909182aaffade79249e1f78915e1d358e69ff5cb Mon Sep 17 00:00:00 2001 From: AMIT KUMAR Date: Fri, 26 Oct 2018 12:04:48 +0530 Subject: [PATCH 01/18] Issue #SB-0000 feat:keycloak submit otp page issue --- .../keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index df31ba8d..fcb35181 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -123,6 +123,7 @@ private void sendEmail(AuthenticationFlowContext context) { } String actionTokenUserId = authenticationSession.getAuthNote(DefaultActionTokenKey.ACTION_TOKEN_USER_ID); + authenticationSession.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.name()); if (actionTokenUserId != null && Objects.equals(user.getId(), actionTokenUserId)) { logger.debugf("Forget-password triggered when reauthenticating user after authentication via action token. Skipping " + CREDENTIAL_TYPE + " screen and using user '%s' ", user.getUsername()); context.success(); From 6cbbeeac6d6199c92e484199214826751cd3d19e Mon Sep 17 00:00:00 2001 From: AMIT KUMAR Date: Thu, 22 Nov 2018 11:58:37 +0530 Subject: [PATCH 02/18] Issue #SB-3853 feat: sending otp via email --- .../sms/KeycloakSmsAuthenticator.java | 49 ++++++++++++++--- .../org/sunbird/keycloak/utils/Constants.java | 21 +++++++- .../sunbird/keycloak/utils/HttpClient.java | 53 +++++++++++++++++++ 3 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index fcb35181..63e922dc 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -1,7 +1,11 @@ package org.sunbird.keycloak.resetcredential.sms; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; import javax.ws.rs.core.MultivaluedMap; @@ -32,6 +36,8 @@ import org.keycloak.services.ServicesLogger; import org.keycloak.services.messages.Messages; import org.keycloak.sessions.AuthenticationSessionModel; +import org.sunbird.keycloak.utils.Constants; +import org.sunbird.keycloak.utils.HttpClient; /** * Created by joris on 11/11/2016. @@ -66,13 +72,19 @@ public void authenticate(AuthenticationFlowContext context) { } if (StringUtils.isNotBlank(mobileNumber) || StringUtils.isNotBlank(userEmail)) { - if (StringUtils.isNotBlank(userEmail)) { + if (StringUtils.isBlank(mobileNumber) && StringUtils.isNotBlank(userEmail)) { logger.debug("KeycloakSmsAuthenticator@authenticate - Sending Email - " + userEmail); sendEmail(context); - } - if (StringUtils.isNotBlank(mobileNumber)) { - logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); - sendSMS(context, mobileNumber); + }else{ + Map otpRespone = null; + if (StringUtils.isNotBlank(mobileNumber)) { + logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); + otpRespone = sendSMS(context, mobileNumber); + } + if (StringUtils.isNotBlank(userEmail)) { + logger.debug("KeycloakSmsAuthenticator@authenticate - Sending Email via sunbird - " + userEmail); + sendEmailViaSunbird(otpRespone,userEmail); + } } } else { // The mobile number is NOT configured --> complain @@ -83,7 +95,26 @@ public void authenticate(AuthenticationFlowContext context) { } } - private void sendSMS(AuthenticationFlowContext context, String mobileNumber) { + private void sendEmailViaSunbird(Map otpResponse, String userEmail) { + if(null != otpResponse){ + List emails = new ArrayList<>(Arrays.asList(userEmail)); + otpResponse.put(Constants.RECIPIENT_EMAILS, emails); + otpResponse.put(Constants.SUBJECT, Constants.MAIL_SUBJECT); + otpResponse.put(Constants.ORG_NAME, System.getenv(Constants.SUNBIRD_INSTALLATION_DISPLAY_NAME)); + otpResponse.put(Constants.EMAIL_TEMPLATE_TYPE, Constants.RESET_PASSWORD_EMAIL_TEMPLATE); + otpResponse.put(Constants.BODY, Constants.BODY); + + logger.debug("KeycloakSmsAuthenticator@sendEmailViaSunbird - Sending Email - " + userEmail); + Map request = new HashMap<>(); + request.put(Constants.REQUEST, otpResponse); + + HttpClient.post(request, + (Constants.SUNBIRD_WEB_URL + Constants.SEND_NOTIFICATION_URI), + System.getenv(Constants.SUNBIRD_LMS_AUTHORIZATION)); + } + } + + private Map sendSMS(AuthenticationFlowContext context, String mobileNumber) { // The mobile number is configured --> send an SMS long nrOfDigits = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_LENGTH, 8L); logger.debug("Using nrOfDigits " + nrOfDigits); @@ -93,19 +124,21 @@ private void sendSMS(AuthenticationFlowContext context, String mobileNumber) { long ttl = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_TTL, 10 * 60L); // 10 minutes in s logger.debug("Using ttl " + ttl + " (s)"); - + Map response = new HashMap<>(); String code = KeycloakSmsAuthenticatorUtil.getSmsCode(nrOfDigits); - storeSMSCode(context, code, new Date().getTime() + (ttl * 1000)); // s --> ms if (KeycloakSmsAuthenticatorUtil.sendSmsCode(mobileNumber, code, context.getAuthenticatorConfig())) { Response challenge = context.form().createForm("sms-validation.ftl"); context.challenge(challenge); + response.put(Constants.OTP, code); + response.put(Constants.TTL, (ttl/60)); } else { Response challenge = context.form() .setError("SMS could not be sent.") .createForm("sms-validation-error.ftl"); context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR, challenge); } + return response; } private void sendEmail(AuthenticationFlowContext context) { diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java index 0840ad4c..af035b91 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java @@ -28,5 +28,24 @@ private Constants(){} public static final String ERROR_REALM_ADMIN_ROLE_ACCESS = "Does not have realm admin role."; public static final String ERROR_INVALID_PARAMETER_VALUE = "Invalid value {0} for parameter {1}."; public static final String ERROR_MANDATORY_PARAM_MISSING = "Mandatory parameter {0} is missing."; - + public static final String OTP = "otp"; + public static final String ACCEPT = "Accept"; + public static final String APPLICATION_JSON = "application/json"; + public static final String CONTENT_TYPE = "Content-type"; + public static final String EMAIL = "email"; + public static final String AUTHORIZATION = "Authorization"; + public static final String TTL = "ttl"; + public static final String SUNBIRD_LMS_AUTHORIZATION = "sunbird_authorization"; + + public static final String SUNBIRD_INSTALLATION_DISPLAY_NAME = "sunbird_installation_display_name"; + public static final String MAIL_SUBJECT = "Reset password"; + public static final String SUBJECT = "subject"; + public static final String EMAIL_TEMPLATE_TYPE = "emailTemplateType"; + public static final String ORG_NAME = "accountName"; + public static final String SEND_NOTIFICATION_URI = "/v1/notification/email"; + public static final String SUNBIRD_WEB_URL = "http://localhost:9000"; + public static final String BODY = "body"; + public static final String RECIPIENT_EMAILS = "recipientEmails"; + public static final String RESET_PASSWORD_EMAIL_TEMPLATE = "resetPassword"; + public static final String REQUEST = "request"; } diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java new file mode 100644 index 00000000..83f42a6f --- /dev/null +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java @@ -0,0 +1,53 @@ +package org.sunbird.keycloak.utils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.jboss.logging.Logger; + +public class HttpClient { + + private static Logger logger = Logger.getLogger(HttpClient.class); + + private HttpClient(){} + + public static HttpResponse post(Map requestBody,String uri, String authorizationKey){ + logger.debug("HttpClient@post - post method called "); + CloseableHttpClient client = null; + try { + ObjectMapper mapper = new ObjectMapper(); + client = HttpClients.createDefault(); + HttpPost httpPost = new HttpPost(uri); + logger.debug("HttpClient@post: uri :"+uri); + String authKey = Constants.BEARER+" "+authorizationKey; + StringEntity entity = new StringEntity(mapper.writeValueAsString(requestBody)); + logger.debug("HttpClient@post: request entity :"+entity); + httpPost.setEntity(entity); + httpPost.setHeader(Constants.ACCEPT, Constants.APPLICATION_JSON); + httpPost.setHeader(Constants.CONTENT_TYPE, Constants.APPLICATION_JSON); + if(StringUtils.isNotBlank(authKey)){ + httpPost.setHeader(Constants.AUTHORIZATION,authKey); + } + CloseableHttpResponse response = client.execute(httpPost); + logger.debug("HttpClient@post: response.getStatusLine().getStatusCode() :"+response.getStatusLine().getStatusCode()); + return response; + } catch (Exception e) { + logger.error("HttpClient:post : Exception occurred :" + e); + } finally { + if(null != client) + try { + client.close(); + } catch (Exception e) { + logger.error("HttpClient:post : Exception occurred while closing client:" + e); + } + } + return null; + + } +} From 041673cd4e1d5f161e82be9e57c99b0a3f27da6c Mon Sep 17 00:00:00 2001 From: AMIT KUMAR Date: Thu, 22 Nov 2018 12:47:30 +0530 Subject: [PATCH 03/18] Issue #SB-3853 feat: sending otp via email --- .../resetcredential/sms/KeycloakSmsAuthenticator.java | 2 +- .../src/main/java/org/sunbird/keycloak/utils/Constants.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index 63e922dc..1c4a5283 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -109,7 +109,7 @@ private void sendEmailViaSunbird(Map otpResponse, String userEma request.put(Constants.REQUEST, otpResponse); HttpClient.post(request, - (Constants.SUNBIRD_WEB_URL + Constants.SEND_NOTIFICATION_URI), + (System.getenv(Constants.SUNBIRD_WEB_URL) + Constants.SEND_NOTIFICATION_URI), System.getenv(Constants.SUNBIRD_LMS_AUTHORIZATION)); } } diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java index af035b91..ade2b248 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java @@ -42,8 +42,8 @@ private Constants(){} public static final String SUBJECT = "subject"; public static final String EMAIL_TEMPLATE_TYPE = "emailTemplateType"; public static final String ORG_NAME = "accountName"; - public static final String SEND_NOTIFICATION_URI = "/v1/notification/email"; - public static final String SUNBIRD_WEB_URL = "http://localhost:9000"; + public static final String SEND_NOTIFICATION_URI = "/user/v1/notification/email"; + public static final String SUNBIRD_WEB_URL = "sunbird_web_url"; public static final String BODY = "body"; public static final String RECIPIENT_EMAILS = "recipientEmails"; public static final String RESET_PASSWORD_EMAIL_TEMPLATE = "resetPassword"; From 2e14c61dca2c524f88006a59076982b45882ffc9 Mon Sep 17 00:00:00 2001 From: AMIT KUMAR Date: Thu, 22 Nov 2018 16:42:19 +0530 Subject: [PATCH 04/18] Issue #SB-3853 feat: sending otp via email --- .../sms/KeycloakSmsAuthenticator.java | 505 +++++++++--------- .../sunbird/keycloak/utils/HttpClient.java | 34 +- 2 files changed, 277 insertions(+), 262 deletions(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index 1c4a5283..b2f0c002 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -44,293 +44,316 @@ */ public class KeycloakSmsAuthenticator implements Authenticator { - private static Logger logger = Logger.getLogger(KeycloakSmsAuthenticator.class); + private static Logger logger = Logger.getLogger(KeycloakSmsAuthenticator.class); - public static final String CREDENTIAL_TYPE = "sms_validation"; + public static final String CREDENTIAL_TYPE = "sms_validation"; - private enum CODE_STATUS { - VALID, - INVALID, - EXPIRED - } + private enum CODE_STATUS { + VALID, INVALID, EXPIRED + } - @Override - public void authenticate(AuthenticationFlowContext context) { - logger.debug("KeycloakSmsAuthenticator@authenticate called ... context = " + context); + @Override + public void authenticate(AuthenticationFlowContext context) { + logger.debug("KeycloakSmsAuthenticator@authenticate called ... context = " + context); - UserModel user = context.getUser(); - logger.debug("KeycloakSmsAuthenticator@authenticate - User = " + user.getUsername()); + UserModel user = context.getUser(); + logger.debug("KeycloakSmsAuthenticator@authenticate - User = " + user.getUsername()); - List mobileNumberCreds = user.getAttribute(KeycloakSmsAuthenticatorConstants.ATTR_MOBILE); + List mobileNumberCreds = + user.getAttribute(KeycloakSmsAuthenticatorConstants.ATTR_MOBILE); - String mobileNumber = null; - String userEmail = user.getEmail(); + String mobileNumber = null; + String userEmail = user.getEmail(); - if (mobileNumberCreds != null && !mobileNumberCreds.isEmpty()) { - mobileNumber = mobileNumberCreds.get(0); - } + if (mobileNumberCreds != null && !mobileNumberCreds.isEmpty()) { + mobileNumber = mobileNumberCreds.get(0); + } - if (StringUtils.isNotBlank(mobileNumber) || StringUtils.isNotBlank(userEmail)) { - if (StringUtils.isBlank(mobileNumber) && StringUtils.isNotBlank(userEmail)) { - logger.debug("KeycloakSmsAuthenticator@authenticate - Sending Email - " + userEmail); - sendEmail(context); - }else{ - Map otpRespone = null; - if (StringUtils.isNotBlank(mobileNumber)) { - logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); - otpRespone = sendSMS(context, mobileNumber); - } - if (StringUtils.isNotBlank(userEmail)) { - logger.debug("KeycloakSmsAuthenticator@authenticate - Sending Email via sunbird - " + userEmail); - sendEmailViaSunbird(otpRespone,userEmail); - } - } - } else { - // The mobile number is NOT configured --> complain - Response challenge = context.form().setError("Missing mobile number and email!") - .createForm("sms-validation-error.ftl"); - context.failureChallenge(AuthenticationFlowError.CLIENT_CREDENTIALS_SETUP_REQUIRED, - challenge); + if (StringUtils.isNotBlank(mobileNumber) || StringUtils.isNotBlank(userEmail)) { + if (StringUtils.isBlank(mobileNumber)) { + logger.debug("KeycloakSmsAuthenticator@authenticate - Sending Email - " + userEmail); + sendEmail(context); + } else { + Map otpRespone = null; + if (StringUtils.isNotBlank(mobileNumber)) { + logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); + otpRespone = sendSMS(context, mobileNumber); } + if (StringUtils.isNotBlank(userEmail)) { + logger.debug( + "KeycloakSmsAuthenticator@authenticate - Sending Email via sunbird - " + userEmail); + sendEmailViaSunbird(otpRespone, userEmail); + } + } + } else { + // The mobile number is NOT configured --> complain + Response challenge = context.form().setError("Missing mobile number and email!") + .createForm("sms-validation-error.ftl"); + context.failureChallenge(AuthenticationFlowError.CLIENT_CREDENTIALS_SETUP_REQUIRED, + challenge); } + } private void sendEmailViaSunbird(Map otpResponse, String userEmail) { - if(null != otpResponse){ - List emails = new ArrayList<>(Arrays.asList(userEmail)); - otpResponse.put(Constants.RECIPIENT_EMAILS, emails); - otpResponse.put(Constants.SUBJECT, Constants.MAIL_SUBJECT); - otpResponse.put(Constants.ORG_NAME, System.getenv(Constants.SUNBIRD_INSTALLATION_DISPLAY_NAME)); - otpResponse.put(Constants.EMAIL_TEMPLATE_TYPE, Constants.RESET_PASSWORD_EMAIL_TEMPLATE); - otpResponse.put(Constants.BODY, Constants.BODY); - - logger.debug("KeycloakSmsAuthenticator@sendEmailViaSunbird - Sending Email - " + userEmail); - Map request = new HashMap<>(); - request.put(Constants.REQUEST, otpResponse); - - HttpClient.post(request, - (System.getenv(Constants.SUNBIRD_WEB_URL) + Constants.SEND_NOTIFICATION_URI), - System.getenv(Constants.SUNBIRD_LMS_AUTHORIZATION)); + if (null != otpResponse) { + List emails = new ArrayList<>(Arrays.asList(userEmail)); + otpResponse.put(Constants.RECIPIENT_EMAILS, emails); + otpResponse.put(Constants.SUBJECT, Constants.MAIL_SUBJECT); + otpResponse.put(Constants.ORG_NAME, + System.getenv(Constants.SUNBIRD_INSTALLATION_DISPLAY_NAME)); + otpResponse.put(Constants.EMAIL_TEMPLATE_TYPE, Constants.RESET_PASSWORD_EMAIL_TEMPLATE); + otpResponse.put(Constants.BODY, Constants.BODY); + + logger.debug("KeycloakSmsAuthenticator@sendEmailViaSunbird - Sending Email - " + userEmail); + Map request = new HashMap<>(); + request.put(Constants.REQUEST, otpResponse); + + HttpClient.post(request, + (System.getenv(Constants.SUNBIRD_WEB_URL) + Constants.SEND_NOTIFICATION_URI), + System.getenv(Constants.SUNBIRD_LMS_AUTHORIZATION)); } } - private Map sendSMS(AuthenticationFlowContext context, String mobileNumber) { - // The mobile number is configured --> send an SMS - long nrOfDigits = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_LENGTH, 8L); - logger.debug("Using nrOfDigits " + nrOfDigits); + private Map sendSMS(AuthenticationFlowContext context, String mobileNumber) { + // The mobile number is configured --> send an SMS + long nrOfDigits = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), + KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_LENGTH, 8L); + logger.debug("Using nrOfDigits " + nrOfDigits); + + logger.debug("KeycloakSmsAuthenticator@sendSMS"); + + long ttl = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), + KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_TTL, 10 * 60L); // 10 minutes in s + + logger.debug("Using ttl " + ttl + " (s)"); + Map response = new HashMap<>(); + String code = KeycloakSmsAuthenticatorUtil.getSmsCode(nrOfDigits); + storeSMSCode(context, code, new Date().getTime() + (ttl * 1000)); // s --> ms + if (KeycloakSmsAuthenticatorUtil.sendSmsCode(mobileNumber, code, + context.getAuthenticatorConfig())) { + Response challenge = context.form().createForm("sms-validation.ftl"); + context.challenge(challenge); + response.put(Constants.OTP, code); + response.put(Constants.TTL, (ttl / 60)); + } else { + Response challenge = + context.form().setError("SMS could not be sent.").createForm("sms-validation-error.ftl"); + context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR, challenge); + } + return response; + } - logger.debug("KeycloakSmsAuthenticator@sendSMS"); + private void sendEmail(AuthenticationFlowContext context) { + logger.debug("KeycloakSmsAuthenticator@sendEmail"); - long ttl = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_TTL, 10 * 60L); // 10 minutes in s + UserModel user = context.getUser(); + AuthenticationSessionModel authenticationSession = context.getAuthenticationSession(); + String username = + authenticationSession.getAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME); - logger.debug("Using ttl " + ttl + " (s)"); - Map response = new HashMap<>(); - String code = KeycloakSmsAuthenticatorUtil.getSmsCode(nrOfDigits); - storeSMSCode(context, code, new Date().getTime() + (ttl * 1000)); // s --> ms - if (KeycloakSmsAuthenticatorUtil.sendSmsCode(mobileNumber, code, context.getAuthenticatorConfig())) { - Response challenge = context.form().createForm("sms-validation.ftl"); - context.challenge(challenge); - response.put(Constants.OTP, code); - response.put(Constants.TTL, (ttl/60)); - } else { - Response challenge = context.form() - .setError("SMS could not be sent.") - .createForm("sms-validation-error.ftl"); - context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR, challenge); - } - return response; + // we don't want people guessing usernames, so if there was a problem obtaining the user, the + // user will be null. + // just reset login for with a success message + if (user == null) { + context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); + return; } - private void sendEmail(AuthenticationFlowContext context) { - logger.debug("KeycloakSmsAuthenticator@sendEmail"); + String actionTokenUserId = + authenticationSession.getAuthNote(DefaultActionTokenKey.ACTION_TOKEN_USER_ID); + authenticationSession.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.name()); + if (actionTokenUserId != null && Objects.equals(user.getId(), actionTokenUserId)) { + logger.debugf( + "Forget-password triggered when reauthenticating user after authentication via action token. Skipping " + + CREDENTIAL_TYPE + " screen and using user '%s' ", + user.getUsername()); + context.success(); + return; + } - UserModel user = context.getUser(); - AuthenticationSessionModel authenticationSession = context.getAuthenticationSession(); - String username = authenticationSession.getAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME); - // we don't want people guessing usernames, so if there was a problem obtaining the user, the user will be null. - // just reset login for with a success message - if (user == null) { - context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); - return; - } - - String actionTokenUserId = authenticationSession.getAuthNote(DefaultActionTokenKey.ACTION_TOKEN_USER_ID); - authenticationSession.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.name()); - if (actionTokenUserId != null && Objects.equals(user.getId(), actionTokenUserId)) { - logger.debugf("Forget-password triggered when reauthenticating user after authentication via action token. Skipping " + CREDENTIAL_TYPE + " screen and using user '%s' ", user.getUsername()); - context.success(); - return; - } + EventBuilder event = context.getEvent(); + // we don't want people guessing usernames, so if there is a problem, just continuously + // challenge + if (user.getEmail() == null || user.getEmail().trim().length() == 0) { + event.user(user).detail(Details.USERNAME, username).error(Errors.INVALID_EMAIL); + context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); + return; + } - EventBuilder event = context.getEvent(); - // we don't want people guessing usernames, so if there is a problem, just continuously challenge - if (user.getEmail() == null || user.getEmail().trim().length() == 0) { - event.user(user) - .detail(Details.USERNAME, username) - .error(Errors.INVALID_EMAIL); + int validityInSecs = context.getRealm().getActionTokenGeneratedByUserLifespan(); + int absoluteExpirationInSecs = Time.currentTime() + validityInSecs; + + // We send the secret in the email in a link as a query param. + ResetCredentialsActionToken token = new ResetCredentialsActionToken(user.getId(), + absoluteExpirationInSecs, authenticationSession.getId()); + String link = UriBuilder + .fromUri(context.getActionTokenUrl( + token.serialize(context.getSession(), context.getRealm(), context.getUriInfo()))) + .build().toString(); + long expirationInMinutes = TimeUnit.SECONDS.toMinutes(validityInSecs); + try { + logger.debug("sendEmail - Reset Link : " + link); + + context.getSession().getProvider(EmailTemplateProvider.class).setRealm(context.getRealm()) + .setUser(user).sendPasswordReset(link, expirationInMinutes); + + event.clone().event(EventType.SEND_RESET_PASSWORD).user(user) + .detail(Details.USERNAME, username).detail(Details.EMAIL, user.getEmail()) + .detail(Details.CODE_ID, authenticationSession.getId()).success(); + context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); + + + Response challenge = context.form().createForm("password-reset-email.ftl"); + context.failureChallenge(AuthenticationFlowError.UNKNOWN_USER, challenge); + + } catch (EmailException e) { + event.clone().event(EventType.SEND_RESET_PASSWORD).detail(Details.USERNAME, username) + .user(user).error(Errors.EMAIL_SEND_FAILED); + ServicesLogger.LOGGER.failedToSendPwdResetEmail(e); + Response challenge = context.form().setError(Messages.EMAIL_SENT_ERROR).createErrorPage(); + context.failure(AuthenticationFlowError.INTERNAL_ERROR, challenge); + } + } - context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); - return; - } - int validityInSecs = context.getRealm().getActionTokenGeneratedByUserLifespan(); - int absoluteExpirationInSecs = Time.currentTime() + validityInSecs; - - // We send the secret in the email in a link as a query param. - ResetCredentialsActionToken token = new ResetCredentialsActionToken(user.getId(), absoluteExpirationInSecs, authenticationSession.getId()); - String link = UriBuilder - .fromUri(context.getActionTokenUrl(token.serialize(context.getSession(), context.getRealm(), context.getUriInfo()))) - .build() - .toString(); - long expirationInMinutes = TimeUnit.SECONDS.toMinutes(validityInSecs); - try { - logger.debug("sendEmail - Reset Link : " + link); - - context.getSession().getProvider(EmailTemplateProvider.class).setRealm(context.getRealm()).setUser(user).sendPasswordReset(link, expirationInMinutes); - - event.clone().event(EventType.SEND_RESET_PASSWORD) - .user(user) - .detail(Details.USERNAME, username) - .detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, authenticationSession.getId()).success(); - context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); - - - Response challenge = context.form() - .createForm("password-reset-email.ftl"); - context.failureChallenge(AuthenticationFlowError.UNKNOWN_USER, challenge); - - } catch (EmailException e) { - event.clone().event(EventType.SEND_RESET_PASSWORD) - .detail(Details.USERNAME, username) - .user(user) - .error(Errors.EMAIL_SEND_FAILED); - ServicesLogger.LOGGER.failedToSendPwdResetEmail(e); - Response challenge = context.form() - .setError(Messages.EMAIL_SENT_ERROR) - .createErrorPage(); - context.failure(AuthenticationFlowError.INTERNAL_ERROR, challenge); + @Override + public void action(AuthenticationFlowContext context) { + logger.debug("action called ... context = " + context); + logger.debug( + "KeycloakSmsAuthenticator@action called ... for User = " + context.getUser().getUsername()); + CODE_STATUS status = validateCode(context); + Response challenge = null; + switch (status) { + case EXPIRED: + logger.debug("KeycloakSmsAuthenticator@action - EXPIRED"); + challenge = context.form().setError("code is expired").createForm("sms-validation.ftl"); + context.failureChallenge(AuthenticationFlowError.EXPIRED_CODE, challenge); + break; + + case INVALID: + logger.debug("KeycloakSmsAuthenticator@action - INVALID"); + + if (context.getExecution() + .getRequirement() == AuthenticationExecutionModel.Requirement.OPTIONAL + || context.getExecution() + .getRequirement() == AuthenticationExecutionModel.Requirement.ALTERNATIVE) { + logger.debug("KeycloakSmsAuthenticator@action - OPTIONAL || ALTERNATIVE"); + + logger.debug("Calling context.attempted()"); + context.attempted(); + } else if (context.getExecution() + .getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) { + logger.debug("KeycloakSmsAuthenticator@action - INVALID_CREDENTIALS"); + + challenge = context.form().setError("Invalid code specified, please enter it again") + .createForm("sms-validation.ftl"); + context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challenge); + } else { + // Something strange happened + logger.warn("Undefined execution ..."); + logger.debug("KeycloakSmsAuthenticator@action - SOMETHING STRANGE HAPPENED!"); + logger.debug( + "KeycloakSmsAuthenticator@action - " + context.getExecution().getRequirement()); + logger.debug( + "KeycloakSmsAuthenticator@action - " + context.getExecution().getAuthenticator()); + logger.debug("KeycloakSmsAuthenticator@action - " + + context.getExecution().getAuthenticatorConfig()); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getFlowId()); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getId()); + logger + .debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getParentFlow()); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getPriority()); } - } - + break; - @Override - public void action(AuthenticationFlowContext context) { - logger.debug("action called ... context = " + context); - logger.debug("KeycloakSmsAuthenticator@action called ... for User = " + context.getUser().getUsername()); - CODE_STATUS status = validateCode(context); - Response challenge = null; - switch (status) { - case EXPIRED: - logger.debug("KeycloakSmsAuthenticator@action - EXPIRED"); - challenge = context.form() - .setError("code is expired") - .createForm("sms-validation.ftl"); - context.failureChallenge(AuthenticationFlowError.EXPIRED_CODE, challenge); - break; - - case INVALID: - logger.debug("KeycloakSmsAuthenticator@action - INVALID"); - - if (context.getExecution().getRequirement() == AuthenticationExecutionModel.Requirement.OPTIONAL || - context.getExecution().getRequirement() == AuthenticationExecutionModel.Requirement.ALTERNATIVE) { - logger.debug("KeycloakSmsAuthenticator@action - OPTIONAL || ALTERNATIVE"); - - logger.debug("Calling context.attempted()"); - context.attempted(); - } else if (context.getExecution().getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) { - logger.debug("KeycloakSmsAuthenticator@action - INVALID_CREDENTIALS"); - - challenge = context.form() - .setError("Invalid code specified, please enter it again") - .createForm("sms-validation.ftl"); - context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challenge); - } else { - // Something strange happened - logger.warn("Undefined execution ..."); - logger.debug("KeycloakSmsAuthenticator@action - SOMETHING STRANGE HAPPENED!"); - logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getRequirement()); - logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getAuthenticator()); - logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getAuthenticatorConfig()); - logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getFlowId()); - logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getId()); - logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getParentFlow()); - logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getPriority()); - } - break; - - case VALID: - context.success(); - break; + case VALID: + context.success(); + break; - } } + } - // Store the code + expiration time in a UserCredential. Keycloak will persist these in the DB. - // When the code is validated on another node (in a clustered environment) the other nodes have access to it's values too. - private void storeSMSCode(AuthenticationFlowContext context, String code, Long expiringAt) { - logger.debug("KeycloakSmsAuthenticator@storeSMSCode" + "User name = " + context.getUser().getUsername()); - - UserCredentialModel credentials = new UserCredentialModel(); - credentials.setType(KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_CODE); - credentials.setValue(code); + // Store the code + expiration time in a UserCredential. Keycloak will persist these in the DB. + // When the code is validated on another node (in a clustered environment) the other nodes have + // access to it's values too. + private void storeSMSCode(AuthenticationFlowContext context, String code, Long expiringAt) { + logger.debug( + "KeycloakSmsAuthenticator@storeSMSCode" + "User name = " + context.getUser().getUsername()); - context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), credentials); + UserCredentialModel credentials = new UserCredentialModel(); + credentials.setType(KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_CODE); + credentials.setValue(code); - credentials.setType(KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_EXP_TIME); - credentials.setValue((expiringAt).toString()); - context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), credentials); - } + context.getSession().userCredentialManager().updateCredential(context.getRealm(), + context.getUser(), credentials); + credentials.setType(KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_EXP_TIME); + credentials.setValue((expiringAt).toString()); + context.getSession().userCredentialManager().updateCredential(context.getRealm(), + context.getUser(), credentials); + } - protected CODE_STATUS validateCode(AuthenticationFlowContext context) { - logger.debug("KeycloakSmsAuthenticator@validateCode"); - CODE_STATUS result = CODE_STATUS.INVALID; - logger.debug("validateCode called ... "); - MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); - String enteredCode = formData.getFirst(KeycloakSmsAuthenticatorConstants.ANSW_SMS_CODE); - KeycloakSession session = context.getSession(); + protected CODE_STATUS validateCode(AuthenticationFlowContext context) { + logger.debug("KeycloakSmsAuthenticator@validateCode"); + CODE_STATUS result = CODE_STATUS.INVALID; - List codeCreds = session.userCredentialManager().getStoredCredentialsByType(context.getRealm(), context.getUser(), KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_CODE); - /*List timeCreds = session.userCredentialManager().getStoredCredentialsByType(context.getRealm(), context.getUser(), KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_EXP_TIME);*/ + logger.debug("validateCode called ... "); + MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); + String enteredCode = formData.getFirst(KeycloakSmsAuthenticatorConstants.ANSW_SMS_CODE); + KeycloakSession session = context.getSession(); - CredentialModel expectedCode = (CredentialModel) codeCreds.get(0); - /*CredentialModel expTimeString = (CredentialModel) timeCreds.get(0);*/ + List codeCreds = session.userCredentialManager().getStoredCredentialsByType(context.getRealm(), + context.getUser(), KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_CODE); + /* + * List timeCreds = + * session.userCredentialManager().getStoredCredentialsByType(context.getRealm(), + * context.getUser(), KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_EXP_TIME); + */ - logger.debug("KeycloakSmsAuthenticator@validateCode " + "User name = " + context.getUser().getUsername()); - logger.debug("KeycloakSmsAuthenticator@validateCode " + "Expected code = " + expectedCode.getValue() + " entered code = " + enteredCode); + CredentialModel expectedCode = (CredentialModel) codeCreds.get(0); + /* CredentialModel expTimeString = (CredentialModel) timeCreds.get(0); */ - if (expectedCode != null) { - result = enteredCode.equals(expectedCode.getValue()) ? CODE_STATUS.VALID : CODE_STATUS.INVALID; - } - logger.debug("result : " + result); + logger.debug("KeycloakSmsAuthenticator@validateCode " + "User name = " + + context.getUser().getUsername()); + logger.debug("KeycloakSmsAuthenticator@validateCode " + "Expected code = " + + expectedCode.getValue() + " entered code = " + enteredCode); - logger.debug("KeycloakSmsAuthenticator@validateCode- Result -" + result); - return result; + if (expectedCode != null) { + result = + enteredCode.equals(expectedCode.getValue()) ? CODE_STATUS.VALID : CODE_STATUS.INVALID; } + logger.debug("result : " + result); - @Override - public boolean requiresUser() { - logger.debug("requiresUser called ... returning true"); - return true; - } + logger.debug("KeycloakSmsAuthenticator@validateCode- Result -" + result); + return result; + } - @Override - public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { - logger.debug("KeycloakSmsAuthenticator@validateCode configuredFor called ... session=" + session + ", realm=" + realm + ", user=" + user); - return true; - } + @Override + public boolean requiresUser() { + logger.debug("requiresUser called ... returning true"); + return true; + } - @Override - public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { - logger.debug("KeycloakSmsAuthenticator@validateCode - setRequiredActions called ... session=" + session + ", realm=" + realm + ", user=" + user); - } + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + logger.debug("KeycloakSmsAuthenticator@validateCode configuredFor called ... session=" + session + + ", realm=" + realm + ", user=" + user); + return true; + } - @Override - public void close() { - logger.debug("close called ..."); - } + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + logger.debug("KeycloakSmsAuthenticator@validateCode - setRequiredActions called ... session=" + + session + ", realm=" + realm + ", user=" + user); + } + + @Override + public void close() { + logger.debug("close called ..."); + } } diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java index 83f42a6f..04be3f09 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java @@ -14,38 +14,30 @@ public class HttpClient { private static Logger logger = Logger.getLogger(HttpClient.class); - - private HttpClient(){} - - public static HttpResponse post(Map requestBody,String uri, String authorizationKey){ - logger.debug("HttpClient@post - post method called "); - CloseableHttpClient client = null; - try { + + private HttpClient() {} + + public static HttpResponse post(Map requestBody, String uri, + String authorizationKey) { + logger.debug("HttpClient:post - post method called "); + try (CloseableHttpClient client = HttpClients.createDefault()) { ObjectMapper mapper = new ObjectMapper(); - client = HttpClients.createDefault(); HttpPost httpPost = new HttpPost(uri); - logger.debug("HttpClient@post: uri :"+uri); - String authKey = Constants.BEARER+" "+authorizationKey; + logger.debug("HttpClient:post: uri :" + uri); + String authKey = Constants.BEARER + " " + authorizationKey; StringEntity entity = new StringEntity(mapper.writeValueAsString(requestBody)); - logger.debug("HttpClient@post: request entity :"+entity); + logger.debug("HttpClient:post: request entity :" + entity); httpPost.setEntity(entity); httpPost.setHeader(Constants.ACCEPT, Constants.APPLICATION_JSON); httpPost.setHeader(Constants.CONTENT_TYPE, Constants.APPLICATION_JSON); - if(StringUtils.isNotBlank(authKey)){ - httpPost.setHeader(Constants.AUTHORIZATION,authKey); + if (StringUtils.isNotBlank(authKey)) { + httpPost.setHeader(Constants.AUTHORIZATION, authKey); } CloseableHttpResponse response = client.execute(httpPost); - logger.debug("HttpClient@post: response.getStatusLine().getStatusCode() :"+response.getStatusLine().getStatusCode()); + logger.debug("HttpClient:post: statusCode :" + response.getStatusLine().getStatusCode()); return response; } catch (Exception e) { logger.error("HttpClient:post : Exception occurred :" + e); - } finally { - if(null != client) - try { - client.close(); - } catch (Exception e) { - logger.error("HttpClient:post : Exception occurred while closing client:" + e); - } } return null; From 86165bb0797f3faa7ba0c1c3413730189ef7f669 Mon Sep 17 00:00:00 2001 From: bvinayakumar <36028332+bvinayakumar@users.noreply.github.com> Date: Fri, 23 Nov 2018 12:25:24 +0530 Subject: [PATCH 05/18] Update HttpClient.java --- .../java/org/sunbird/keycloak/utils/HttpClient.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java index 04be3f09..dddbc6df 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java @@ -19,14 +19,14 @@ private HttpClient() {} public static HttpResponse post(Map requestBody, String uri, String authorizationKey) { - logger.debug("HttpClient:post - post method called "); + logger.debug("HttpClient: post called"); try (CloseableHttpClient client = HttpClients.createDefault()) { ObjectMapper mapper = new ObjectMapper(); HttpPost httpPost = new HttpPost(uri); - logger.debug("HttpClient:post: uri :" + uri); + logger.debug("HttpClient:post: uri = " + uri); String authKey = Constants.BEARER + " " + authorizationKey; StringEntity entity = new StringEntity(mapper.writeValueAsString(requestBody)); - logger.debug("HttpClient:post: request entity :" + entity); + logger.debug("HttpClient:post: request entity = " + entity); httpPost.setEntity(entity); httpPost.setHeader(Constants.ACCEPT, Constants.APPLICATION_JSON); httpPost.setHeader(Constants.CONTENT_TYPE, Constants.APPLICATION_JSON); @@ -34,12 +34,12 @@ public static HttpResponse post(Map requestBody, String uri, httpPost.setHeader(Constants.AUTHORIZATION, authKey); } CloseableHttpResponse response = client.execute(httpPost); - logger.debug("HttpClient:post: statusCode :" + response.getStatusLine().getStatusCode()); + logger.debug("HttpClient:post: statusCode = " + response.getStatusLine().getStatusCode()); return response; } catch (Exception e) { - logger.error("HttpClient:post : Exception occurred :" + e); + logger.error("HttpClient:post: Exception occurred = " + e); } return null; - } + } From 02c6a03e4b740a855a1b3fdd480d8dd3ce03bec8 Mon Sep 17 00:00:00 2001 From: AMIT KUMAR Date: Fri, 23 Nov 2018 12:43:25 +0530 Subject: [PATCH 06/18] Issue #SB-3853 feat: sending otp via email --- .../sms/KeycloakSmsAuthenticator.java | 15 ++++++++------- .../org/sunbird/keycloak/utils/Constants.java | 5 +---- .../org/sunbird/keycloak/utils/HttpClient.java | 6 ++++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index b2f0c002..aa1e1315 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -75,15 +75,17 @@ public void authenticate(AuthenticationFlowContext context) { logger.debug("KeycloakSmsAuthenticator@authenticate - Sending Email - " + userEmail); sendEmail(context); } else { - Map otpRespone = null; + Map otpResponse = null; if (StringUtils.isNotBlank(mobileNumber)) { logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); - otpRespone = sendSMS(context, mobileNumber); + otpResponse = sendSMS(context, mobileNumber); } if (StringUtils.isNotBlank(userEmail)) { logger.debug( "KeycloakSmsAuthenticator@authenticate - Sending Email via sunbird - " + userEmail); - sendEmailViaSunbird(otpRespone, userEmail); + logger.debug( + "KeycloakSmsAuthenticator@authenticate - realmName - " + context.getRealm().getDisplayName()); + sendEmailViaSunbird(otpResponse, userEmail, context.getRealm().getDisplayName()); } } } else { @@ -95,14 +97,13 @@ public void authenticate(AuthenticationFlowContext context) { } } - private void sendEmailViaSunbird(Map otpResponse, String userEmail) { + private void sendEmailViaSunbird(Map otpResponse, String userEmail, String realmName) { if (null != otpResponse) { List emails = new ArrayList<>(Arrays.asList(userEmail)); otpResponse.put(Constants.RECIPIENT_EMAILS, emails); otpResponse.put(Constants.SUBJECT, Constants.MAIL_SUBJECT); - otpResponse.put(Constants.ORG_NAME, - System.getenv(Constants.SUNBIRD_INSTALLATION_DISPLAY_NAME)); - otpResponse.put(Constants.EMAIL_TEMPLATE_TYPE, Constants.RESET_PASSWORD_EMAIL_TEMPLATE); + otpResponse.put(Constants.ORG_NAME, realmName); + otpResponse.put(Constants.EMAIL_TEMPLATE_TYPE, Constants.FORGOT_PASSWORD_EMAIL_TEMPLATE); otpResponse.put(Constants.BODY, Constants.BODY); logger.debug("KeycloakSmsAuthenticator@sendEmailViaSunbird - Sending Email - " + userEmail); diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java index ade2b248..6efbaa9c 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java @@ -29,9 +29,6 @@ private Constants(){} public static final String ERROR_INVALID_PARAMETER_VALUE = "Invalid value {0} for parameter {1}."; public static final String ERROR_MANDATORY_PARAM_MISSING = "Mandatory parameter {0} is missing."; public static final String OTP = "otp"; - public static final String ACCEPT = "Accept"; - public static final String APPLICATION_JSON = "application/json"; - public static final String CONTENT_TYPE = "Content-type"; public static final String EMAIL = "email"; public static final String AUTHORIZATION = "Authorization"; public static final String TTL = "ttl"; @@ -46,6 +43,6 @@ private Constants(){} public static final String SUNBIRD_WEB_URL = "sunbird_web_url"; public static final String BODY = "body"; public static final String RECIPIENT_EMAILS = "recipientEmails"; - public static final String RESET_PASSWORD_EMAIL_TEMPLATE = "resetPassword"; + public static final String FORGOT_PASSWORD_EMAIL_TEMPLATE = "forgotPasswordWithOTP"; public static final String REQUEST = "request"; } diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java index dddbc6df..bbfc6c20 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Map; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; @@ -28,8 +30,8 @@ public static HttpResponse post(Map requestBody, String uri, StringEntity entity = new StringEntity(mapper.writeValueAsString(requestBody)); logger.debug("HttpClient:post: request entity = " + entity); httpPost.setEntity(entity); - httpPost.setHeader(Constants.ACCEPT, Constants.APPLICATION_JSON); - httpPost.setHeader(Constants.CONTENT_TYPE, Constants.APPLICATION_JSON); + httpPost.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON); + httpPost.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); if (StringUtils.isNotBlank(authKey)) { httpPost.setHeader(Constants.AUTHORIZATION, authKey); } From b192d8b0a75a07f11bb3de50a765fd124f06c459 Mon Sep 17 00:00:00 2001 From: AMIT KUMAR Date: Fri, 23 Nov 2018 13:21:45 +0530 Subject: [PATCH 07/18] Issue #SB-3853 feat: sending otp via email --- .../resetcredential/sms/KeycloakSmsAuthenticator.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index aa1e1315..61c373dc 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -76,10 +76,9 @@ public void authenticate(AuthenticationFlowContext context) { sendEmail(context); } else { Map otpResponse = null; - if (StringUtils.isNotBlank(mobileNumber)) { - logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); - otpResponse = sendSMS(context, mobileNumber); - } + logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); + otpResponse = sendSMS(context, mobileNumber); + if (StringUtils.isNotBlank(userEmail)) { logger.debug( "KeycloakSmsAuthenticator@authenticate - Sending Email via sunbird - " + userEmail); From 2f66356a3c4d084e717bfe7f7fceabd358d1b677 Mon Sep 17 00:00:00 2001 From: AMIT KUMAR Date: Fri, 23 Nov 2018 14:38:04 +0530 Subject: [PATCH 08/18] Issue #SB-3853 feat:otp support --- .../keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java | 2 +- .../src/main/java/org/sunbird/keycloak/utils/Constants.java | 3 +-- .../src/main/java/org/sunbird/keycloak/utils/HttpClient.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index 61c373dc..09138dda 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -101,7 +101,7 @@ private void sendEmailViaSunbird(Map otpResponse, String userEma List emails = new ArrayList<>(Arrays.asList(userEmail)); otpResponse.put(Constants.RECIPIENT_EMAILS, emails); otpResponse.put(Constants.SUBJECT, Constants.MAIL_SUBJECT); - otpResponse.put(Constants.ORG_NAME, realmName); + otpResponse.put(Constants.REALM_NAME, realmName); otpResponse.put(Constants.EMAIL_TEMPLATE_TYPE, Constants.FORGOT_PASSWORD_EMAIL_TEMPLATE); otpResponse.put(Constants.BODY, Constants.BODY); diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java index 6efbaa9c..387579ca 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java @@ -30,7 +30,6 @@ private Constants(){} public static final String ERROR_MANDATORY_PARAM_MISSING = "Mandatory parameter {0} is missing."; public static final String OTP = "otp"; public static final String EMAIL = "email"; - public static final String AUTHORIZATION = "Authorization"; public static final String TTL = "ttl"; public static final String SUNBIRD_LMS_AUTHORIZATION = "sunbird_authorization"; @@ -38,7 +37,7 @@ private Constants(){} public static final String MAIL_SUBJECT = "Reset password"; public static final String SUBJECT = "subject"; public static final String EMAIL_TEMPLATE_TYPE = "emailTemplateType"; - public static final String ORG_NAME = "accountName"; + public static final String REALM_NAME = "realmName"; public static final String SEND_NOTIFICATION_URI = "/user/v1/notification/email"; public static final String SUNBIRD_WEB_URL = "sunbird_web_url"; public static final String BODY = "body"; diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java index bbfc6c20..f5d30faa 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/HttpClient.java @@ -33,7 +33,7 @@ public static HttpResponse post(Map requestBody, String uri, httpPost.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON); httpPost.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); if (StringUtils.isNotBlank(authKey)) { - httpPost.setHeader(Constants.AUTHORIZATION, authKey); + httpPost.setHeader(HttpHeaders.AUTHORIZATION, authKey); } CloseableHttpResponse response = client.execute(httpPost); logger.debug("HttpClient:post: statusCode = " + response.getStatusLine().getStatusCode()); From af24257e0162bd1f71f60cfe67b1da8e7c9816af Mon Sep 17 00:00:00 2001 From: bvinayakumar <36028332+bvinayakumar@users.noreply.github.com> Date: Fri, 23 Nov 2018 17:11:00 +0530 Subject: [PATCH 09/18] Update Constants.java --- .../src/main/java/org/sunbird/keycloak/utils/Constants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java index 387579ca..497e3d3c 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java @@ -39,7 +39,7 @@ private Constants(){} public static final String EMAIL_TEMPLATE_TYPE = "emailTemplateType"; public static final String REALM_NAME = "realmName"; public static final String SEND_NOTIFICATION_URI = "/user/v1/notification/email"; - public static final String SUNBIRD_WEB_URL = "sunbird_web_url"; + public static final String SUNBIRD_LMS_BASE_URL = "sunbird_lms_base_url"; public static final String BODY = "body"; public static final String RECIPIENT_EMAILS = "recipientEmails"; public static final String FORGOT_PASSWORD_EMAIL_TEMPLATE = "forgotPasswordWithOTP"; From 5bc23ba8b70d5519bd6f5b099b57db0c6a5611c8 Mon Sep 17 00:00:00 2001 From: bvinayakumar <36028332+bvinayakumar@users.noreply.github.com> Date: Fri, 23 Nov 2018 17:11:36 +0530 Subject: [PATCH 10/18] Update KeycloakSmsAuthenticator.java --- .../keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index 09138dda..129b6715 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -110,7 +110,7 @@ private void sendEmailViaSunbird(Map otpResponse, String userEma request.put(Constants.REQUEST, otpResponse); HttpClient.post(request, - (System.getenv(Constants.SUNBIRD_WEB_URL) + Constants.SEND_NOTIFICATION_URI), + (System.getenv(Constants.SUNBIRD_LMS_BASE_URL) + Constants.SEND_NOTIFICATION_URI), System.getenv(Constants.SUNBIRD_LMS_AUTHORIZATION)); } } From b091f9ab0e7051e2cbb64f5fa54d7932528bcd37 Mon Sep 17 00:00:00 2001 From: AMIT KUMAR Date: Tue, 27 Nov 2018 17:24:39 +0530 Subject: [PATCH 11/18] Issue #SB-8891 feat:sending otp via email --- .../sms/KeycloakSmsAuthenticator.java | 484 +++++++----------- .../org/sunbird/keycloak/utils/Constants.java | 1 - 2 files changed, 194 insertions(+), 291 deletions(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index 129b6715..878e0470 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -6,36 +6,20 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeUnit; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriBuilder; import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpResponse; import org.jboss.logging.Logger; import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.AuthenticationFlowError; import org.keycloak.authentication.Authenticator; -import org.keycloak.authentication.actiontoken.DefaultActionTokenKey; -import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionToken; -import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator; -import org.keycloak.common.util.Time; import org.keycloak.credential.CredentialModel; -import org.keycloak.email.EmailException; -import org.keycloak.email.EmailTemplateProvider; -import org.keycloak.events.Details; -import org.keycloak.events.Errors; -import org.keycloak.events.EventBuilder; -import org.keycloak.events.EventType; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; -import org.keycloak.models.utils.FormMessage; -import org.keycloak.services.ServicesLogger; -import org.keycloak.services.messages.Messages; -import org.keycloak.sessions.AuthenticationSessionModel; import org.sunbird.keycloak.utils.Constants; import org.sunbird.keycloak.utils.HttpClient; @@ -44,64 +28,102 @@ */ public class KeycloakSmsAuthenticator implements Authenticator { - private static Logger logger = Logger.getLogger(KeycloakSmsAuthenticator.class); + private static Logger logger = Logger.getLogger(KeycloakSmsAuthenticator.class); - public static final String CREDENTIAL_TYPE = "sms_validation"; + public static final String CREDENTIAL_TYPE = "sms_validation"; + + private enum CODE_STATUS { + VALID, + INVALID, + EXPIRED + } - private enum CODE_STATUS { - VALID, INVALID, EXPIRED - } + @Override + public void authenticate(AuthenticationFlowContext context) { + logger.debug("KeycloakSmsAuthenticator@authenticate called ... context = " + context); - @Override - public void authenticate(AuthenticationFlowContext context) { - logger.debug("KeycloakSmsAuthenticator@authenticate called ... context = " + context); + UserModel user = context.getUser(); + logger.debug("KeycloakSmsAuthenticator@authenticate - User = " + user.getUsername()); - UserModel user = context.getUser(); - logger.debug("KeycloakSmsAuthenticator@authenticate - User = " + user.getUsername()); + List mobileNumberCreds = user.getAttribute(KeycloakSmsAuthenticatorConstants.ATTR_MOBILE); - List mobileNumberCreds = - user.getAttribute(KeycloakSmsAuthenticatorConstants.ATTR_MOBILE); + String mobileNumber = null; + String userEmail = user.getEmail(); - String mobileNumber = null; - String userEmail = user.getEmail(); + if (mobileNumberCreds != null && !mobileNumberCreds.isEmpty()) { + mobileNumber = mobileNumberCreds.get(0); + } - if (mobileNumberCreds != null && !mobileNumberCreds.isEmpty()) { - mobileNumber = mobileNumberCreds.get(0); + Map otpResponse = generateOtp(context); + if (StringUtils.isNotBlank(mobileNumber) || StringUtils.isNotBlank(userEmail)) { + logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); + if (StringUtils.isNotBlank(mobileNumber)) { + sendSMS(otpResponse, context, mobileNumber); + } + if (StringUtils.isNotBlank(userEmail)) { + logger.debug( + "KeycloakSmsAuthenticator@authenticate - Sending Email via sunbird - " + userEmail); + logger.debug("KeycloakSmsAuthenticator@authenticate - realmName - " + + context.getRealm().getDisplayName()); + sendEmailViaSunbird(otpResponse, context, userEmail); + } + } else { + // The mobile number is NOT configured --> complain + Response challenge = context.form().setError("Missing mobile number and email!") + .createForm("sms-validation-error.ftl"); + context.failureChallenge(AuthenticationFlowError.CLIENT_CREDENTIALS_SETUP_REQUIRED, + challenge); + } } - if (StringUtils.isNotBlank(mobileNumber) || StringUtils.isNotBlank(userEmail)) { - if (StringUtils.isBlank(mobileNumber)) { - logger.debug("KeycloakSmsAuthenticator@authenticate - Sending Email - " + userEmail); - sendEmail(context); + private Map generateOtp(AuthenticationFlowContext context) { + // The mobile number is configured --> send an SMS + long nrOfDigits = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), + KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_LENGTH, 8L); + logger.debug("Using nrOfDigits " + nrOfDigits); + + logger.debug("KeycloakSmsAuthenticator@sendSMS"); + + long ttl = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), + KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_TTL, 10 * 60L); // 10 minutes in s + + logger.debug("Using ttl " + ttl + " (s)"); + String code = KeycloakSmsAuthenticatorUtil.getSmsCode(nrOfDigits); + storeSMSCode(context, code, new Date().getTime() + (ttl * 1000)); // s --> ms + Map response = new HashMap<>(); + response.put(Constants.OTP, code); + response.put(Constants.TTL, (ttl / 60)); + return response; + } + + private void sendSMS(Map otpResponse, AuthenticationFlowContext context, + String mobileNumber) { + if (KeycloakSmsAuthenticatorUtil.sendSmsCode(mobileNumber, + (String) otpResponse.get(Constants.OTP), context.getAuthenticatorConfig())) { + setEnterOTPPage(context, true); } else { - Map otpResponse = null; - logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); - otpResponse = sendSMS(context, mobileNumber); - - if (StringUtils.isNotBlank(userEmail)) { - logger.debug( - "KeycloakSmsAuthenticator@authenticate - Sending Email via sunbird - " + userEmail); - logger.debug( - "KeycloakSmsAuthenticator@authenticate - realmName - " + context.getRealm().getDisplayName()); - sendEmailViaSunbird(otpResponse, userEmail, context.getRealm().getDisplayName()); - } + setEnterOTPPage(context, false); } - } else { - // The mobile number is NOT configured --> complain - Response challenge = context.form().setError("Missing mobile number and email!") - .createForm("sms-validation-error.ftl"); - context.failureChallenge(AuthenticationFlowError.CLIENT_CREDENTIALS_SETUP_REQUIRED, - challenge); } - } - private void sendEmailViaSunbird(Map otpResponse, String userEmail, String realmName) { - if (null != otpResponse) { + private void setEnterOTPPage(AuthenticationFlowContext context, Boolean flag) { + if (flag) { + Response challenge = context.form().createForm("sms-validation.ftl"); + context.challenge(challenge); + } else { + Response challenge = + context.form().setError("OTP could not be sent.").createForm("sms-validation-error.ftl"); + context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR, challenge); + } + } + + private void sendEmailViaSunbird(Map otpResponse, + AuthenticationFlowContext context, String userEmail) { List emails = new ArrayList<>(Arrays.asList(userEmail)); otpResponse.put(Constants.RECIPIENT_EMAILS, emails); otpResponse.put(Constants.SUBJECT, Constants.MAIL_SUBJECT); - otpResponse.put(Constants.REALM_NAME, realmName); + otpResponse.put(Constants.REALM_NAME, context.getRealm().getDisplayName()); otpResponse.put(Constants.EMAIL_TEMPLATE_TYPE, Constants.FORGOT_PASSWORD_EMAIL_TEMPLATE); otpResponse.put(Constants.BODY, Constants.BODY); @@ -109,251 +131,133 @@ private void sendEmailViaSunbird(Map otpResponse, String userEma Map request = new HashMap<>(); request.put(Constants.REQUEST, otpResponse); - HttpClient.post(request, + HttpResponse response = HttpClient.post(request, (System.getenv(Constants.SUNBIRD_LMS_BASE_URL) + Constants.SEND_NOTIFICATION_URI), System.getenv(Constants.SUNBIRD_LMS_AUTHORIZATION)); + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode == 200) { + setEnterOTPPage(context, true); + } else { + setEnterOTPPage(context, false); + } } - } - - private Map sendSMS(AuthenticationFlowContext context, String mobileNumber) { - // The mobile number is configured --> send an SMS - long nrOfDigits = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), - KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_LENGTH, 8L); - logger.debug("Using nrOfDigits " + nrOfDigits); - - logger.debug("KeycloakSmsAuthenticator@sendSMS"); - - long ttl = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), - KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_TTL, 10 * 60L); // 10 minutes in s - - logger.debug("Using ttl " + ttl + " (s)"); - Map response = new HashMap<>(); - String code = KeycloakSmsAuthenticatorUtil.getSmsCode(nrOfDigits); - storeSMSCode(context, code, new Date().getTime() + (ttl * 1000)); // s --> ms - if (KeycloakSmsAuthenticatorUtil.sendSmsCode(mobileNumber, code, - context.getAuthenticatorConfig())) { - Response challenge = context.form().createForm("sms-validation.ftl"); - context.challenge(challenge); - response.put(Constants.OTP, code); - response.put(Constants.TTL, (ttl / 60)); - } else { - Response challenge = - context.form().setError("SMS could not be sent.").createForm("sms-validation-error.ftl"); - context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR, challenge); - } - return response; - } - - private void sendEmail(AuthenticationFlowContext context) { - logger.debug("KeycloakSmsAuthenticator@sendEmail"); - - UserModel user = context.getUser(); - AuthenticationSessionModel authenticationSession = context.getAuthenticationSession(); - String username = - authenticationSession.getAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME); - - // we don't want people guessing usernames, so if there was a problem obtaining the user, the - // user will be null. - // just reset login for with a success message - if (user == null) { - context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); - return; + + @Override + public void action(AuthenticationFlowContext context) { + logger.debug("action called ... context = " + context); + logger.debug("KeycloakSmsAuthenticator@action called ... for User = " + context.getUser().getUsername()); + CODE_STATUS status = validateCode(context); + Response challenge = null; + switch (status) { + case EXPIRED: + logger.debug("KeycloakSmsAuthenticator@action - EXPIRED"); + challenge = context.form() + .setError("code is expired") + .createForm("sms-validation.ftl"); + context.failureChallenge(AuthenticationFlowError.EXPIRED_CODE, challenge); + break; + + case INVALID: + logger.debug("KeycloakSmsAuthenticator@action - INVALID"); + + if (context.getExecution().getRequirement() == AuthenticationExecutionModel.Requirement.OPTIONAL || + context.getExecution().getRequirement() == AuthenticationExecutionModel.Requirement.ALTERNATIVE) { + logger.debug("KeycloakSmsAuthenticator@action - OPTIONAL || ALTERNATIVE"); + + logger.debug("Calling context.attempted()"); + context.attempted(); + } else if (context.getExecution().getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) { + logger.debug("KeycloakSmsAuthenticator@action - INVALID_CREDENTIALS"); + + challenge = context.form() + .setError("Invalid code specified, please enter it again") + .createForm("sms-validation.ftl"); + context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challenge); + } else { + // Something strange happened + logger.warn("Undefined execution ..."); + logger.debug("KeycloakSmsAuthenticator@action - SOMETHING STRANGE HAPPENED!"); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getRequirement()); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getAuthenticator()); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getAuthenticatorConfig()); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getFlowId()); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getId()); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getParentFlow()); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getPriority()); + } + break; + + case VALID: + context.success(); + break; + + } } - String actionTokenUserId = - authenticationSession.getAuthNote(DefaultActionTokenKey.ACTION_TOKEN_USER_ID); - authenticationSession.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.name()); - if (actionTokenUserId != null && Objects.equals(user.getId(), actionTokenUserId)) { - logger.debugf( - "Forget-password triggered when reauthenticating user after authentication via action token. Skipping " - + CREDENTIAL_TYPE + " screen and using user '%s' ", - user.getUsername()); - context.success(); - return; + // Store the code + expiration time in a UserCredential. Keycloak will persist these in the DB. + // When the code is validated on another node (in a clustered environment) the other nodes have access to it's values too. + private void storeSMSCode(AuthenticationFlowContext context, String code, Long expiringAt) { + logger.debug("KeycloakSmsAuthenticator@storeSMSCode" + "User name = " + context.getUser().getUsername()); + + UserCredentialModel credentials = new UserCredentialModel(); + credentials.setType(KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_CODE); + credentials.setValue(code); + + context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), credentials); + + credentials.setType(KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_EXP_TIME); + credentials.setValue((expiringAt).toString()); + context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), credentials); } - EventBuilder event = context.getEvent(); - // we don't want people guessing usernames, so if there is a problem, just continuously - // challenge - if (user.getEmail() == null || user.getEmail().trim().length() == 0) { - event.user(user).detail(Details.USERNAME, username).error(Errors.INVALID_EMAIL); + protected CODE_STATUS validateCode(AuthenticationFlowContext context) { + logger.debug("KeycloakSmsAuthenticator@validateCode"); + CODE_STATUS result = CODE_STATUS.INVALID; + + logger.debug("validateCode called ... "); + MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); + String enteredCode = formData.getFirst(KeycloakSmsAuthenticatorConstants.ANSW_SMS_CODE); + KeycloakSession session = context.getSession(); + + List codeCreds = session.userCredentialManager().getStoredCredentialsByType(context.getRealm(), context.getUser(), KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_CODE); + /*List timeCreds = session.userCredentialManager().getStoredCredentialsByType(context.getRealm(), context.getUser(), KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_EXP_TIME);*/ - context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); - return; + CredentialModel expectedCode = (CredentialModel) codeCreds.get(0); + /*CredentialModel expTimeString = (CredentialModel) timeCreds.get(0);*/ + + logger.debug("KeycloakSmsAuthenticator@validateCode " + "User name = " + context.getUser().getUsername()); + logger.debug("KeycloakSmsAuthenticator@validateCode " + "Expected code = " + expectedCode.getValue() + " entered code = " + enteredCode); + + if (expectedCode != null) { + result = enteredCode.equals(expectedCode.getValue()) ? CODE_STATUS.VALID : CODE_STATUS.INVALID; + } + logger.debug("result : " + result); + + logger.debug("KeycloakSmsAuthenticator@validateCode- Result -" + result); + return result; } - int validityInSecs = context.getRealm().getActionTokenGeneratedByUserLifespan(); - int absoluteExpirationInSecs = Time.currentTime() + validityInSecs; - - // We send the secret in the email in a link as a query param. - ResetCredentialsActionToken token = new ResetCredentialsActionToken(user.getId(), - absoluteExpirationInSecs, authenticationSession.getId()); - String link = UriBuilder - .fromUri(context.getActionTokenUrl( - token.serialize(context.getSession(), context.getRealm(), context.getUriInfo()))) - .build().toString(); - long expirationInMinutes = TimeUnit.SECONDS.toMinutes(validityInSecs); - try { - logger.debug("sendEmail - Reset Link : " + link); - - context.getSession().getProvider(EmailTemplateProvider.class).setRealm(context.getRealm()) - .setUser(user).sendPasswordReset(link, expirationInMinutes); - - event.clone().event(EventType.SEND_RESET_PASSWORD).user(user) - .detail(Details.USERNAME, username).detail(Details.EMAIL, user.getEmail()) - .detail(Details.CODE_ID, authenticationSession.getId()).success(); - context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); - - - Response challenge = context.form().createForm("password-reset-email.ftl"); - context.failureChallenge(AuthenticationFlowError.UNKNOWN_USER, challenge); - - } catch (EmailException e) { - event.clone().event(EventType.SEND_RESET_PASSWORD).detail(Details.USERNAME, username) - .user(user).error(Errors.EMAIL_SEND_FAILED); - ServicesLogger.LOGGER.failedToSendPwdResetEmail(e); - Response challenge = context.form().setError(Messages.EMAIL_SENT_ERROR).createErrorPage(); - context.failure(AuthenticationFlowError.INTERNAL_ERROR, challenge); + @Override + public boolean requiresUser() { + logger.debug("requiresUser called ... returning true"); + return true; } - } - - - @Override - public void action(AuthenticationFlowContext context) { - logger.debug("action called ... context = " + context); - logger.debug( - "KeycloakSmsAuthenticator@action called ... for User = " + context.getUser().getUsername()); - CODE_STATUS status = validateCode(context); - Response challenge = null; - switch (status) { - case EXPIRED: - logger.debug("KeycloakSmsAuthenticator@action - EXPIRED"); - challenge = context.form().setError("code is expired").createForm("sms-validation.ftl"); - context.failureChallenge(AuthenticationFlowError.EXPIRED_CODE, challenge); - break; - - case INVALID: - logger.debug("KeycloakSmsAuthenticator@action - INVALID"); - - if (context.getExecution() - .getRequirement() == AuthenticationExecutionModel.Requirement.OPTIONAL - || context.getExecution() - .getRequirement() == AuthenticationExecutionModel.Requirement.ALTERNATIVE) { - logger.debug("KeycloakSmsAuthenticator@action - OPTIONAL || ALTERNATIVE"); - - logger.debug("Calling context.attempted()"); - context.attempted(); - } else if (context.getExecution() - .getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) { - logger.debug("KeycloakSmsAuthenticator@action - INVALID_CREDENTIALS"); - - challenge = context.form().setError("Invalid code specified, please enter it again") - .createForm("sms-validation.ftl"); - context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challenge); - } else { - // Something strange happened - logger.warn("Undefined execution ..."); - logger.debug("KeycloakSmsAuthenticator@action - SOMETHING STRANGE HAPPENED!"); - logger.debug( - "KeycloakSmsAuthenticator@action - " + context.getExecution().getRequirement()); - logger.debug( - "KeycloakSmsAuthenticator@action - " + context.getExecution().getAuthenticator()); - logger.debug("KeycloakSmsAuthenticator@action - " - + context.getExecution().getAuthenticatorConfig()); - logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getFlowId()); - logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getId()); - logger - .debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getParentFlow()); - logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getPriority()); - } - break; - case VALID: - context.success(); - break; + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + logger.debug("KeycloakSmsAuthenticator@validateCode configuredFor called ... session=" + session + ", realm=" + realm + ", user=" + user); + return true; + } + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + logger.debug("KeycloakSmsAuthenticator@validateCode - setRequiredActions called ... session=" + session + ", realm=" + realm + ", user=" + user); } - } - - // Store the code + expiration time in a UserCredential. Keycloak will persist these in the DB. - // When the code is validated on another node (in a clustered environment) the other nodes have - // access to it's values too. - private void storeSMSCode(AuthenticationFlowContext context, String code, Long expiringAt) { - logger.debug( - "KeycloakSmsAuthenticator@storeSMSCode" + "User name = " + context.getUser().getUsername()); - - UserCredentialModel credentials = new UserCredentialModel(); - credentials.setType(KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_CODE); - credentials.setValue(code); - - context.getSession().userCredentialManager().updateCredential(context.getRealm(), - context.getUser(), credentials); - - credentials.setType(KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_EXP_TIME); - credentials.setValue((expiringAt).toString()); - context.getSession().userCredentialManager().updateCredential(context.getRealm(), - context.getUser(), credentials); - } - - - protected CODE_STATUS validateCode(AuthenticationFlowContext context) { - logger.debug("KeycloakSmsAuthenticator@validateCode"); - CODE_STATUS result = CODE_STATUS.INVALID; - - logger.debug("validateCode called ... "); - MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); - String enteredCode = formData.getFirst(KeycloakSmsAuthenticatorConstants.ANSW_SMS_CODE); - KeycloakSession session = context.getSession(); - - List codeCreds = session.userCredentialManager().getStoredCredentialsByType(context.getRealm(), - context.getUser(), KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_CODE); - /* - * List timeCreds = - * session.userCredentialManager().getStoredCredentialsByType(context.getRealm(), - * context.getUser(), KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_EXP_TIME); - */ - - CredentialModel expectedCode = (CredentialModel) codeCreds.get(0); - /* CredentialModel expTimeString = (CredentialModel) timeCreds.get(0); */ - - logger.debug("KeycloakSmsAuthenticator@validateCode " + "User name = " - + context.getUser().getUsername()); - logger.debug("KeycloakSmsAuthenticator@validateCode " + "Expected code = " - + expectedCode.getValue() + " entered code = " + enteredCode); - - if (expectedCode != null) { - result = - enteredCode.equals(expectedCode.getValue()) ? CODE_STATUS.VALID : CODE_STATUS.INVALID; + + @Override + public void close() { + logger.debug("close called ..."); } - logger.debug("result : " + result); - - logger.debug("KeycloakSmsAuthenticator@validateCode- Result -" + result); - return result; - } - - @Override - public boolean requiresUser() { - logger.debug("requiresUser called ... returning true"); - return true; - } - - @Override - public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { - logger.debug("KeycloakSmsAuthenticator@validateCode configuredFor called ... session=" + session - + ", realm=" + realm + ", user=" + user); - return true; - } - - @Override - public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { - logger.debug("KeycloakSmsAuthenticator@validateCode - setRequiredActions called ... session=" - + session + ", realm=" + realm + ", user=" + user); - } - - @Override - public void close() { - logger.debug("close called ..."); - } - -} + +} \ No newline at end of file diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java index 497e3d3c..7c7b959f 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/utils/Constants.java @@ -33,7 +33,6 @@ private Constants(){} public static final String TTL = "ttl"; public static final String SUNBIRD_LMS_AUTHORIZATION = "sunbird_authorization"; - public static final String SUNBIRD_INSTALLATION_DISPLAY_NAME = "sunbird_installation_display_name"; public static final String MAIL_SUBJECT = "Reset password"; public static final String SUBJECT = "subject"; public static final String EMAIL_TEMPLATE_TYPE = "emailTemplateType"; From ef0258ac607d9184c6218ec4c4e2eab6cf09efb7 Mon Sep 17 00:00:00 2001 From: AMIT KUMAR Date: Tue, 27 Nov 2018 17:39:48 +0530 Subject: [PATCH 12/18] SB-8891 format change --- .../sms/KeycloakSmsAuthenticator.java | 525 ++++++++---------- 1 file changed, 234 insertions(+), 291 deletions(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index 129b6715..df31ba8d 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -1,11 +1,7 @@ package org.sunbird.keycloak.resetcredential.sms; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; import javax.ws.rs.core.MultivaluedMap; @@ -36,324 +32,271 @@ import org.keycloak.services.ServicesLogger; import org.keycloak.services.messages.Messages; import org.keycloak.sessions.AuthenticationSessionModel; -import org.sunbird.keycloak.utils.Constants; -import org.sunbird.keycloak.utils.HttpClient; /** * Created by joris on 11/11/2016. */ public class KeycloakSmsAuthenticator implements Authenticator { - private static Logger logger = Logger.getLogger(KeycloakSmsAuthenticator.class); + private static Logger logger = Logger.getLogger(KeycloakSmsAuthenticator.class); - public static final String CREDENTIAL_TYPE = "sms_validation"; + public static final String CREDENTIAL_TYPE = "sms_validation"; - private enum CODE_STATUS { - VALID, INVALID, EXPIRED - } + private enum CODE_STATUS { + VALID, + INVALID, + EXPIRED + } - @Override - public void authenticate(AuthenticationFlowContext context) { - logger.debug("KeycloakSmsAuthenticator@authenticate called ... context = " + context); + @Override + public void authenticate(AuthenticationFlowContext context) { + logger.debug("KeycloakSmsAuthenticator@authenticate called ... context = " + context); - UserModel user = context.getUser(); - logger.debug("KeycloakSmsAuthenticator@authenticate - User = " + user.getUsername()); + UserModel user = context.getUser(); + logger.debug("KeycloakSmsAuthenticator@authenticate - User = " + user.getUsername()); - List mobileNumberCreds = - user.getAttribute(KeycloakSmsAuthenticatorConstants.ATTR_MOBILE); + List mobileNumberCreds = user.getAttribute(KeycloakSmsAuthenticatorConstants.ATTR_MOBILE); - String mobileNumber = null; - String userEmail = user.getEmail(); + String mobileNumber = null; + String userEmail = user.getEmail(); - if (mobileNumberCreds != null && !mobileNumberCreds.isEmpty()) { - mobileNumber = mobileNumberCreds.get(0); - } + if (mobileNumberCreds != null && !mobileNumberCreds.isEmpty()) { + mobileNumber = mobileNumberCreds.get(0); + } - if (StringUtils.isNotBlank(mobileNumber) || StringUtils.isNotBlank(userEmail)) { - if (StringUtils.isBlank(mobileNumber)) { - logger.debug("KeycloakSmsAuthenticator@authenticate - Sending Email - " + userEmail); - sendEmail(context); - } else { - Map otpResponse = null; - logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); - otpResponse = sendSMS(context, mobileNumber); - - if (StringUtils.isNotBlank(userEmail)) { - logger.debug( - "KeycloakSmsAuthenticator@authenticate - Sending Email via sunbird - " + userEmail); - logger.debug( - "KeycloakSmsAuthenticator@authenticate - realmName - " + context.getRealm().getDisplayName()); - sendEmailViaSunbird(otpResponse, userEmail, context.getRealm().getDisplayName()); + if (StringUtils.isNotBlank(mobileNumber) || StringUtils.isNotBlank(userEmail)) { + if (StringUtils.isNotBlank(userEmail)) { + logger.debug("KeycloakSmsAuthenticator@authenticate - Sending Email - " + userEmail); + sendEmail(context); + } + if (StringUtils.isNotBlank(mobileNumber)) { + logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); + sendSMS(context, mobileNumber); + } + } else { + // The mobile number is NOT configured --> complain + Response challenge = context.form().setError("Missing mobile number and email!") + .createForm("sms-validation-error.ftl"); + context.failureChallenge(AuthenticationFlowError.CLIENT_CREDENTIALS_SETUP_REQUIRED, + challenge); } - } - } else { - // The mobile number is NOT configured --> complain - Response challenge = context.form().setError("Missing mobile number and email!") - .createForm("sms-validation-error.ftl"); - context.failureChallenge(AuthenticationFlowError.CLIENT_CREDENTIALS_SETUP_REQUIRED, - challenge); } - } - - private void sendEmailViaSunbird(Map otpResponse, String userEmail, String realmName) { - if (null != otpResponse) { - List emails = new ArrayList<>(Arrays.asList(userEmail)); - otpResponse.put(Constants.RECIPIENT_EMAILS, emails); - otpResponse.put(Constants.SUBJECT, Constants.MAIL_SUBJECT); - otpResponse.put(Constants.REALM_NAME, realmName); - otpResponse.put(Constants.EMAIL_TEMPLATE_TYPE, Constants.FORGOT_PASSWORD_EMAIL_TEMPLATE); - otpResponse.put(Constants.BODY, Constants.BODY); - - logger.debug("KeycloakSmsAuthenticator@sendEmailViaSunbird - Sending Email - " + userEmail); - Map request = new HashMap<>(); - request.put(Constants.REQUEST, otpResponse); - - HttpClient.post(request, - (System.getenv(Constants.SUNBIRD_LMS_BASE_URL) + Constants.SEND_NOTIFICATION_URI), - System.getenv(Constants.SUNBIRD_LMS_AUTHORIZATION)); + + private void sendSMS(AuthenticationFlowContext context, String mobileNumber) { + // The mobile number is configured --> send an SMS + long nrOfDigits = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_LENGTH, 8L); + logger.debug("Using nrOfDigits " + nrOfDigits); + + logger.debug("KeycloakSmsAuthenticator@sendSMS"); + + long ttl = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_TTL, 10 * 60L); // 10 minutes in s + + logger.debug("Using ttl " + ttl + " (s)"); + + String code = KeycloakSmsAuthenticatorUtil.getSmsCode(nrOfDigits); + + storeSMSCode(context, code, new Date().getTime() + (ttl * 1000)); // s --> ms + if (KeycloakSmsAuthenticatorUtil.sendSmsCode(mobileNumber, code, context.getAuthenticatorConfig())) { + Response challenge = context.form().createForm("sms-validation.ftl"); + context.challenge(challenge); + } else { + Response challenge = context.form() + .setError("SMS could not be sent.") + .createForm("sms-validation-error.ftl"); + context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR, challenge); + } } - } - - private Map sendSMS(AuthenticationFlowContext context, String mobileNumber) { - // The mobile number is configured --> send an SMS - long nrOfDigits = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), - KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_LENGTH, 8L); - logger.debug("Using nrOfDigits " + nrOfDigits); - - logger.debug("KeycloakSmsAuthenticator@sendSMS"); - - long ttl = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), - KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_TTL, 10 * 60L); // 10 minutes in s - - logger.debug("Using ttl " + ttl + " (s)"); - Map response = new HashMap<>(); - String code = KeycloakSmsAuthenticatorUtil.getSmsCode(nrOfDigits); - storeSMSCode(context, code, new Date().getTime() + (ttl * 1000)); // s --> ms - if (KeycloakSmsAuthenticatorUtil.sendSmsCode(mobileNumber, code, - context.getAuthenticatorConfig())) { - Response challenge = context.form().createForm("sms-validation.ftl"); - context.challenge(challenge); - response.put(Constants.OTP, code); - response.put(Constants.TTL, (ttl / 60)); - } else { - Response challenge = - context.form().setError("SMS could not be sent.").createForm("sms-validation-error.ftl"); - context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR, challenge); + + private void sendEmail(AuthenticationFlowContext context) { + logger.debug("KeycloakSmsAuthenticator@sendEmail"); + + UserModel user = context.getUser(); + AuthenticationSessionModel authenticationSession = context.getAuthenticationSession(); + String username = authenticationSession.getAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME); + + // we don't want people guessing usernames, so if there was a problem obtaining the user, the user will be null. + // just reset login for with a success message + if (user == null) { + context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); + return; + } + + String actionTokenUserId = authenticationSession.getAuthNote(DefaultActionTokenKey.ACTION_TOKEN_USER_ID); + if (actionTokenUserId != null && Objects.equals(user.getId(), actionTokenUserId)) { + logger.debugf("Forget-password triggered when reauthenticating user after authentication via action token. Skipping " + CREDENTIAL_TYPE + " screen and using user '%s' ", user.getUsername()); + context.success(); + return; + } + + + EventBuilder event = context.getEvent(); + // we don't want people guessing usernames, so if there is a problem, just continuously challenge + if (user.getEmail() == null || user.getEmail().trim().length() == 0) { + event.user(user) + .detail(Details.USERNAME, username) + .error(Errors.INVALID_EMAIL); + + context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); + return; + } + + int validityInSecs = context.getRealm().getActionTokenGeneratedByUserLifespan(); + int absoluteExpirationInSecs = Time.currentTime() + validityInSecs; + + // We send the secret in the email in a link as a query param. + ResetCredentialsActionToken token = new ResetCredentialsActionToken(user.getId(), absoluteExpirationInSecs, authenticationSession.getId()); + String link = UriBuilder + .fromUri(context.getActionTokenUrl(token.serialize(context.getSession(), context.getRealm(), context.getUriInfo()))) + .build() + .toString(); + long expirationInMinutes = TimeUnit.SECONDS.toMinutes(validityInSecs); + try { + logger.debug("sendEmail - Reset Link : " + link); + + context.getSession().getProvider(EmailTemplateProvider.class).setRealm(context.getRealm()).setUser(user).sendPasswordReset(link, expirationInMinutes); + + event.clone().event(EventType.SEND_RESET_PASSWORD) + .user(user) + .detail(Details.USERNAME, username) + .detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, authenticationSession.getId()).success(); + context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); + + + Response challenge = context.form() + .createForm("password-reset-email.ftl"); + context.failureChallenge(AuthenticationFlowError.UNKNOWN_USER, challenge); + + } catch (EmailException e) { + event.clone().event(EventType.SEND_RESET_PASSWORD) + .detail(Details.USERNAME, username) + .user(user) + .error(Errors.EMAIL_SEND_FAILED); + ServicesLogger.LOGGER.failedToSendPwdResetEmail(e); + Response challenge = context.form() + .setError(Messages.EMAIL_SENT_ERROR) + .createErrorPage(); + context.failure(AuthenticationFlowError.INTERNAL_ERROR, challenge); + } } - return response; - } - - private void sendEmail(AuthenticationFlowContext context) { - logger.debug("KeycloakSmsAuthenticator@sendEmail"); - - UserModel user = context.getUser(); - AuthenticationSessionModel authenticationSession = context.getAuthenticationSession(); - String username = - authenticationSession.getAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME); - - // we don't want people guessing usernames, so if there was a problem obtaining the user, the - // user will be null. - // just reset login for with a success message - if (user == null) { - context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); - return; + + + @Override + public void action(AuthenticationFlowContext context) { + logger.debug("action called ... context = " + context); + logger.debug("KeycloakSmsAuthenticator@action called ... for User = " + context.getUser().getUsername()); + CODE_STATUS status = validateCode(context); + Response challenge = null; + switch (status) { + case EXPIRED: + logger.debug("KeycloakSmsAuthenticator@action - EXPIRED"); + challenge = context.form() + .setError("code is expired") + .createForm("sms-validation.ftl"); + context.failureChallenge(AuthenticationFlowError.EXPIRED_CODE, challenge); + break; + + case INVALID: + logger.debug("KeycloakSmsAuthenticator@action - INVALID"); + + if (context.getExecution().getRequirement() == AuthenticationExecutionModel.Requirement.OPTIONAL || + context.getExecution().getRequirement() == AuthenticationExecutionModel.Requirement.ALTERNATIVE) { + logger.debug("KeycloakSmsAuthenticator@action - OPTIONAL || ALTERNATIVE"); + + logger.debug("Calling context.attempted()"); + context.attempted(); + } else if (context.getExecution().getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) { + logger.debug("KeycloakSmsAuthenticator@action - INVALID_CREDENTIALS"); + + challenge = context.form() + .setError("Invalid code specified, please enter it again") + .createForm("sms-validation.ftl"); + context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challenge); + } else { + // Something strange happened + logger.warn("Undefined execution ..."); + logger.debug("KeycloakSmsAuthenticator@action - SOMETHING STRANGE HAPPENED!"); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getRequirement()); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getAuthenticator()); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getAuthenticatorConfig()); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getFlowId()); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getId()); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getParentFlow()); + logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getPriority()); + } + break; + + case VALID: + context.success(); + break; + + } } - String actionTokenUserId = - authenticationSession.getAuthNote(DefaultActionTokenKey.ACTION_TOKEN_USER_ID); - authenticationSession.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.name()); - if (actionTokenUserId != null && Objects.equals(user.getId(), actionTokenUserId)) { - logger.debugf( - "Forget-password triggered when reauthenticating user after authentication via action token. Skipping " - + CREDENTIAL_TYPE + " screen and using user '%s' ", - user.getUsername()); - context.success(); - return; + // Store the code + expiration time in a UserCredential. Keycloak will persist these in the DB. + // When the code is validated on another node (in a clustered environment) the other nodes have access to it's values too. + private void storeSMSCode(AuthenticationFlowContext context, String code, Long expiringAt) { + logger.debug("KeycloakSmsAuthenticator@storeSMSCode" + "User name = " + context.getUser().getUsername()); + + UserCredentialModel credentials = new UserCredentialModel(); + credentials.setType(KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_CODE); + credentials.setValue(code); + + context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), credentials); + + credentials.setType(KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_EXP_TIME); + credentials.setValue((expiringAt).toString()); + context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), credentials); } - EventBuilder event = context.getEvent(); - // we don't want people guessing usernames, so if there is a problem, just continuously - // challenge - if (user.getEmail() == null || user.getEmail().trim().length() == 0) { - event.user(user).detail(Details.USERNAME, username).error(Errors.INVALID_EMAIL); + protected CODE_STATUS validateCode(AuthenticationFlowContext context) { + logger.debug("KeycloakSmsAuthenticator@validateCode"); + CODE_STATUS result = CODE_STATUS.INVALID; - context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); - return; + logger.debug("validateCode called ... "); + MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); + String enteredCode = formData.getFirst(KeycloakSmsAuthenticatorConstants.ANSW_SMS_CODE); + KeycloakSession session = context.getSession(); + + List codeCreds = session.userCredentialManager().getStoredCredentialsByType(context.getRealm(), context.getUser(), KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_CODE); + /*List timeCreds = session.userCredentialManager().getStoredCredentialsByType(context.getRealm(), context.getUser(), KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_EXP_TIME);*/ + + CredentialModel expectedCode = (CredentialModel) codeCreds.get(0); + /*CredentialModel expTimeString = (CredentialModel) timeCreds.get(0);*/ + + logger.debug("KeycloakSmsAuthenticator@validateCode " + "User name = " + context.getUser().getUsername()); + logger.debug("KeycloakSmsAuthenticator@validateCode " + "Expected code = " + expectedCode.getValue() + " entered code = " + enteredCode); + + if (expectedCode != null) { + result = enteredCode.equals(expectedCode.getValue()) ? CODE_STATUS.VALID : CODE_STATUS.INVALID; + } + logger.debug("result : " + result); + + logger.debug("KeycloakSmsAuthenticator@validateCode- Result -" + result); + return result; } - int validityInSecs = context.getRealm().getActionTokenGeneratedByUserLifespan(); - int absoluteExpirationInSecs = Time.currentTime() + validityInSecs; - - // We send the secret in the email in a link as a query param. - ResetCredentialsActionToken token = new ResetCredentialsActionToken(user.getId(), - absoluteExpirationInSecs, authenticationSession.getId()); - String link = UriBuilder - .fromUri(context.getActionTokenUrl( - token.serialize(context.getSession(), context.getRealm(), context.getUriInfo()))) - .build().toString(); - long expirationInMinutes = TimeUnit.SECONDS.toMinutes(validityInSecs); - try { - logger.debug("sendEmail - Reset Link : " + link); - - context.getSession().getProvider(EmailTemplateProvider.class).setRealm(context.getRealm()) - .setUser(user).sendPasswordReset(link, expirationInMinutes); - - event.clone().event(EventType.SEND_RESET_PASSWORD).user(user) - .detail(Details.USERNAME, username).detail(Details.EMAIL, user.getEmail()) - .detail(Details.CODE_ID, authenticationSession.getId()).success(); - context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); - - - Response challenge = context.form().createForm("password-reset-email.ftl"); - context.failureChallenge(AuthenticationFlowError.UNKNOWN_USER, challenge); - - } catch (EmailException e) { - event.clone().event(EventType.SEND_RESET_PASSWORD).detail(Details.USERNAME, username) - .user(user).error(Errors.EMAIL_SEND_FAILED); - ServicesLogger.LOGGER.failedToSendPwdResetEmail(e); - Response challenge = context.form().setError(Messages.EMAIL_SENT_ERROR).createErrorPage(); - context.failure(AuthenticationFlowError.INTERNAL_ERROR, challenge); + @Override + public boolean requiresUser() { + logger.debug("requiresUser called ... returning true"); + return true; } - } - - - @Override - public void action(AuthenticationFlowContext context) { - logger.debug("action called ... context = " + context); - logger.debug( - "KeycloakSmsAuthenticator@action called ... for User = " + context.getUser().getUsername()); - CODE_STATUS status = validateCode(context); - Response challenge = null; - switch (status) { - case EXPIRED: - logger.debug("KeycloakSmsAuthenticator@action - EXPIRED"); - challenge = context.form().setError("code is expired").createForm("sms-validation.ftl"); - context.failureChallenge(AuthenticationFlowError.EXPIRED_CODE, challenge); - break; - - case INVALID: - logger.debug("KeycloakSmsAuthenticator@action - INVALID"); - - if (context.getExecution() - .getRequirement() == AuthenticationExecutionModel.Requirement.OPTIONAL - || context.getExecution() - .getRequirement() == AuthenticationExecutionModel.Requirement.ALTERNATIVE) { - logger.debug("KeycloakSmsAuthenticator@action - OPTIONAL || ALTERNATIVE"); - - logger.debug("Calling context.attempted()"); - context.attempted(); - } else if (context.getExecution() - .getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) { - logger.debug("KeycloakSmsAuthenticator@action - INVALID_CREDENTIALS"); - - challenge = context.form().setError("Invalid code specified, please enter it again") - .createForm("sms-validation.ftl"); - context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challenge); - } else { - // Something strange happened - logger.warn("Undefined execution ..."); - logger.debug("KeycloakSmsAuthenticator@action - SOMETHING STRANGE HAPPENED!"); - logger.debug( - "KeycloakSmsAuthenticator@action - " + context.getExecution().getRequirement()); - logger.debug( - "KeycloakSmsAuthenticator@action - " + context.getExecution().getAuthenticator()); - logger.debug("KeycloakSmsAuthenticator@action - " - + context.getExecution().getAuthenticatorConfig()); - logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getFlowId()); - logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getId()); - logger - .debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getParentFlow()); - logger.debug("KeycloakSmsAuthenticator@action - " + context.getExecution().getPriority()); - } - break; - case VALID: - context.success(); - break; + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + logger.debug("KeycloakSmsAuthenticator@validateCode configuredFor called ... session=" + session + ", realm=" + realm + ", user=" + user); + return true; + } + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + logger.debug("KeycloakSmsAuthenticator@validateCode - setRequiredActions called ... session=" + session + ", realm=" + realm + ", user=" + user); } - } - - // Store the code + expiration time in a UserCredential. Keycloak will persist these in the DB. - // When the code is validated on another node (in a clustered environment) the other nodes have - // access to it's values too. - private void storeSMSCode(AuthenticationFlowContext context, String code, Long expiringAt) { - logger.debug( - "KeycloakSmsAuthenticator@storeSMSCode" + "User name = " + context.getUser().getUsername()); - - UserCredentialModel credentials = new UserCredentialModel(); - credentials.setType(KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_CODE); - credentials.setValue(code); - - context.getSession().userCredentialManager().updateCredential(context.getRealm(), - context.getUser(), credentials); - - credentials.setType(KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_EXP_TIME); - credentials.setValue((expiringAt).toString()); - context.getSession().userCredentialManager().updateCredential(context.getRealm(), - context.getUser(), credentials); - } - - - protected CODE_STATUS validateCode(AuthenticationFlowContext context) { - logger.debug("KeycloakSmsAuthenticator@validateCode"); - CODE_STATUS result = CODE_STATUS.INVALID; - - logger.debug("validateCode called ... "); - MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); - String enteredCode = formData.getFirst(KeycloakSmsAuthenticatorConstants.ANSW_SMS_CODE); - KeycloakSession session = context.getSession(); - - List codeCreds = session.userCredentialManager().getStoredCredentialsByType(context.getRealm(), - context.getUser(), KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_CODE); - /* - * List timeCreds = - * session.userCredentialManager().getStoredCredentialsByType(context.getRealm(), - * context.getUser(), KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_EXP_TIME); - */ - - CredentialModel expectedCode = (CredentialModel) codeCreds.get(0); - /* CredentialModel expTimeString = (CredentialModel) timeCreds.get(0); */ - - logger.debug("KeycloakSmsAuthenticator@validateCode " + "User name = " - + context.getUser().getUsername()); - logger.debug("KeycloakSmsAuthenticator@validateCode " + "Expected code = " - + expectedCode.getValue() + " entered code = " + enteredCode); - - if (expectedCode != null) { - result = - enteredCode.equals(expectedCode.getValue()) ? CODE_STATUS.VALID : CODE_STATUS.INVALID; + + @Override + public void close() { + logger.debug("close called ..."); } - logger.debug("result : " + result); - - logger.debug("KeycloakSmsAuthenticator@validateCode- Result -" + result); - return result; - } - - @Override - public boolean requiresUser() { - logger.debug("requiresUser called ... returning true"); - return true; - } - - @Override - public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { - logger.debug("KeycloakSmsAuthenticator@validateCode configuredFor called ... session=" + session - + ", realm=" + realm + ", user=" + user); - return true; - } - - @Override - public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { - logger.debug("KeycloakSmsAuthenticator@validateCode - setRequiredActions called ... session=" - + session + ", realm=" + realm + ", user=" + user); - } - - @Override - public void close() { - logger.debug("close called ..."); - } } From 5f0bfe7c0383e3f1a68136e6efb69afab6fbe22a Mon Sep 17 00:00:00 2001 From: AMIT KUMAR Date: Tue, 27 Nov 2018 17:44:56 +0530 Subject: [PATCH 13/18] SB-8891 feat:formatting --- .../resetcredential/sms/KeycloakSmsAuthenticator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index 878e0470..f86263d6 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -193,7 +193,7 @@ public void action(AuthenticationFlowContext context) { } } - + // Store the code + expiration time in a UserCredential. Keycloak will persist these in the DB. // When the code is validated on another node (in a clustered environment) the other nodes have access to it's values too. private void storeSMSCode(AuthenticationFlowContext context, String code, Long expiringAt) { @@ -260,4 +260,4 @@ public void close() { logger.debug("close called ..."); } -} \ No newline at end of file +} From b225684b44fec84c7a9fb6fc938f978fe8cb08f0 Mon Sep 17 00:00:00 2001 From: Vinaya Kumar B Date: Tue, 27 Nov 2018 17:51:45 +0530 Subject: [PATCH 14/18] Issue #SB-000 fix: Revert file from master --- .../sms/KeycloakSmsAuthenticator.java | 191 +++++++++++------- 1 file changed, 115 insertions(+), 76 deletions(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index f86263d6..df31ba8d 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -1,27 +1,37 @@ package org.sunbird.keycloak.resetcredential.sms; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; -import java.util.HashMap; import java.util.List; -import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpResponse; import org.jboss.logging.Logger; import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.AuthenticationFlowError; import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.actiontoken.DefaultActionTokenKey; +import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionToken; +import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator; +import org.keycloak.common.util.Time; import org.keycloak.credential.CredentialModel; +import org.keycloak.email.EmailException; +import org.keycloak.email.EmailTemplateProvider; +import org.keycloak.events.Details; +import org.keycloak.events.Errors; +import org.keycloak.events.EventBuilder; +import org.keycloak.events.EventType; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; -import org.sunbird.keycloak.utils.Constants; -import org.sunbird.keycloak.utils.HttpClient; +import org.keycloak.models.utils.FormMessage; +import org.keycloak.services.ServicesLogger; +import org.keycloak.services.messages.Messages; +import org.keycloak.sessions.AuthenticationSessionModel; /** * Created by joris on 11/11/2016. @@ -55,18 +65,14 @@ public void authenticate(AuthenticationFlowContext context) { mobileNumber = mobileNumberCreds.get(0); } - Map otpResponse = generateOtp(context); if (StringUtils.isNotBlank(mobileNumber) || StringUtils.isNotBlank(userEmail)) { - logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); - if (StringUtils.isNotBlank(mobileNumber)) { - sendSMS(otpResponse, context, mobileNumber); - } if (StringUtils.isNotBlank(userEmail)) { - logger.debug( - "KeycloakSmsAuthenticator@authenticate - Sending Email via sunbird - " + userEmail); - logger.debug("KeycloakSmsAuthenticator@authenticate - realmName - " - + context.getRealm().getDisplayName()); - sendEmailViaSunbird(otpResponse, context, userEmail); + logger.debug("KeycloakSmsAuthenticator@authenticate - Sending Email - " + userEmail); + sendEmail(context); + } + if (StringUtils.isNotBlank(mobileNumber)) { + logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); + sendSMS(context, mobileNumber); } } else { // The mobile number is NOT configured --> complain @@ -77,71 +83,104 @@ public void authenticate(AuthenticationFlowContext context) { } } - private Map generateOtp(AuthenticationFlowContext context) { - // The mobile number is configured --> send an SMS - long nrOfDigits = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), - KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_LENGTH, 8L); - logger.debug("Using nrOfDigits " + nrOfDigits); + private void sendSMS(AuthenticationFlowContext context, String mobileNumber) { + // The mobile number is configured --> send an SMS + long nrOfDigits = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_LENGTH, 8L); + logger.debug("Using nrOfDigits " + nrOfDigits); - logger.debug("KeycloakSmsAuthenticator@sendSMS"); + logger.debug("KeycloakSmsAuthenticator@sendSMS"); - long ttl = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), - KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_TTL, 10 * 60L); // 10 minutes in s + long ttl = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_TTL, 10 * 60L); // 10 minutes in s - logger.debug("Using ttl " + ttl + " (s)"); - String code = KeycloakSmsAuthenticatorUtil.getSmsCode(nrOfDigits); - storeSMSCode(context, code, new Date().getTime() + (ttl * 1000)); // s --> ms - Map response = new HashMap<>(); - response.put(Constants.OTP, code); - response.put(Constants.TTL, (ttl / 60)); - return response; - } - - private void sendSMS(Map otpResponse, AuthenticationFlowContext context, - String mobileNumber) { - if (KeycloakSmsAuthenticatorUtil.sendSmsCode(mobileNumber, - (String) otpResponse.get(Constants.OTP), context.getAuthenticatorConfig())) { - setEnterOTPPage(context, true); - } else { - setEnterOTPPage(context, false); - } - } + logger.debug("Using ttl " + ttl + " (s)"); - private void setEnterOTPPage(AuthenticationFlowContext context, Boolean flag) { - if (flag) { - Response challenge = context.form().createForm("sms-validation.ftl"); - context.challenge(challenge); - } else { - Response challenge = - context.form().setError("OTP could not be sent.").createForm("sms-validation-error.ftl"); - context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR, challenge); - } + String code = KeycloakSmsAuthenticatorUtil.getSmsCode(nrOfDigits); + + storeSMSCode(context, code, new Date().getTime() + (ttl * 1000)); // s --> ms + if (KeycloakSmsAuthenticatorUtil.sendSmsCode(mobileNumber, code, context.getAuthenticatorConfig())) { + Response challenge = context.form().createForm("sms-validation.ftl"); + context.challenge(challenge); + } else { + Response challenge = context.form() + .setError("SMS could not be sent.") + .createForm("sms-validation-error.ftl"); + context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR, challenge); + } } - - private void sendEmailViaSunbird(Map otpResponse, - AuthenticationFlowContext context, String userEmail) { - List emails = new ArrayList<>(Arrays.asList(userEmail)); - otpResponse.put(Constants.RECIPIENT_EMAILS, emails); - otpResponse.put(Constants.SUBJECT, Constants.MAIL_SUBJECT); - otpResponse.put(Constants.REALM_NAME, context.getRealm().getDisplayName()); - otpResponse.put(Constants.EMAIL_TEMPLATE_TYPE, Constants.FORGOT_PASSWORD_EMAIL_TEMPLATE); - otpResponse.put(Constants.BODY, Constants.BODY); - - logger.debug("KeycloakSmsAuthenticator@sendEmailViaSunbird - Sending Email - " + userEmail); - Map request = new HashMap<>(); - request.put(Constants.REQUEST, otpResponse); - - HttpResponse response = HttpClient.post(request, - (System.getenv(Constants.SUNBIRD_LMS_BASE_URL) + Constants.SEND_NOTIFICATION_URI), - System.getenv(Constants.SUNBIRD_LMS_AUTHORIZATION)); - int statusCode = response.getStatusLine().getStatusCode(); - if (statusCode == 200) { - setEnterOTPPage(context, true); - } else { - setEnterOTPPage(context, false); - } + + private void sendEmail(AuthenticationFlowContext context) { + logger.debug("KeycloakSmsAuthenticator@sendEmail"); + + UserModel user = context.getUser(); + AuthenticationSessionModel authenticationSession = context.getAuthenticationSession(); + String username = authenticationSession.getAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME); + + // we don't want people guessing usernames, so if there was a problem obtaining the user, the user will be null. + // just reset login for with a success message + if (user == null) { + context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); + return; + } + + String actionTokenUserId = authenticationSession.getAuthNote(DefaultActionTokenKey.ACTION_TOKEN_USER_ID); + if (actionTokenUserId != null && Objects.equals(user.getId(), actionTokenUserId)) { + logger.debugf("Forget-password triggered when reauthenticating user after authentication via action token. Skipping " + CREDENTIAL_TYPE + " screen and using user '%s' ", user.getUsername()); + context.success(); + return; + } + + + EventBuilder event = context.getEvent(); + // we don't want people guessing usernames, so if there is a problem, just continuously challenge + if (user.getEmail() == null || user.getEmail().trim().length() == 0) { + event.user(user) + .detail(Details.USERNAME, username) + .error(Errors.INVALID_EMAIL); + + context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); + return; + } + + int validityInSecs = context.getRealm().getActionTokenGeneratedByUserLifespan(); + int absoluteExpirationInSecs = Time.currentTime() + validityInSecs; + + // We send the secret in the email in a link as a query param. + ResetCredentialsActionToken token = new ResetCredentialsActionToken(user.getId(), absoluteExpirationInSecs, authenticationSession.getId()); + String link = UriBuilder + .fromUri(context.getActionTokenUrl(token.serialize(context.getSession(), context.getRealm(), context.getUriInfo()))) + .build() + .toString(); + long expirationInMinutes = TimeUnit.SECONDS.toMinutes(validityInSecs); + try { + logger.debug("sendEmail - Reset Link : " + link); + + context.getSession().getProvider(EmailTemplateProvider.class).setRealm(context.getRealm()).setUser(user).sendPasswordReset(link, expirationInMinutes); + + event.clone().event(EventType.SEND_RESET_PASSWORD) + .user(user) + .detail(Details.USERNAME, username) + .detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, authenticationSession.getId()).success(); + context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); + + + Response challenge = context.form() + .createForm("password-reset-email.ftl"); + context.failureChallenge(AuthenticationFlowError.UNKNOWN_USER, challenge); + + } catch (EmailException e) { + event.clone().event(EventType.SEND_RESET_PASSWORD) + .detail(Details.USERNAME, username) + .user(user) + .error(Errors.EMAIL_SEND_FAILED); + ServicesLogger.LOGGER.failedToSendPwdResetEmail(e); + Response challenge = context.form() + .setError(Messages.EMAIL_SENT_ERROR) + .createErrorPage(); + context.failure(AuthenticationFlowError.INTERNAL_ERROR, challenge); + } } + @Override public void action(AuthenticationFlowContext context) { logger.debug("action called ... context = " + context); @@ -193,7 +232,7 @@ public void action(AuthenticationFlowContext context) { } } - + // Store the code + expiration time in a UserCredential. Keycloak will persist these in the DB. // When the code is validated on another node (in a clustered environment) the other nodes have access to it's values too. private void storeSMSCode(AuthenticationFlowContext context, String code, Long expiringAt) { From 09109befea4d4e7095ff2562ffc3472957f31f40 Mon Sep 17 00:00:00 2001 From: Vinaya Kumar B Date: Tue, 27 Nov 2018 18:01:16 +0530 Subject: [PATCH 15/18] Issue #SB-000 fix: Added a blank line --- .../keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index df31ba8d..6afc59db 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -33,6 +33,7 @@ import org.keycloak.services.messages.Messages; import org.keycloak.sessions.AuthenticationSessionModel; + /** * Created by joris on 11/11/2016. */ From dc6724d9f71f919c5cad57589950b4c9f8010af2 Mon Sep 17 00:00:00 2001 From: AMIT KUMAR Date: Tue, 27 Nov 2018 18:07:00 +0530 Subject: [PATCH 16/18] Issue #SB-8891 feat:sending otp via email --- .../sms/KeycloakSmsAuthenticator.java | 191 +++++++----------- 1 file changed, 76 insertions(+), 115 deletions(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index df31ba8d..878e0470 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -1,37 +1,27 @@ package org.sunbird.keycloak.resetcredential.sms; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; +import java.util.HashMap; import java.util.List; -import java.util.Objects; -import java.util.concurrent.TimeUnit; +import java.util.Map; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriBuilder; import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpResponse; import org.jboss.logging.Logger; import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.AuthenticationFlowError; import org.keycloak.authentication.Authenticator; -import org.keycloak.authentication.actiontoken.DefaultActionTokenKey; -import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionToken; -import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator; -import org.keycloak.common.util.Time; import org.keycloak.credential.CredentialModel; -import org.keycloak.email.EmailException; -import org.keycloak.email.EmailTemplateProvider; -import org.keycloak.events.Details; -import org.keycloak.events.Errors; -import org.keycloak.events.EventBuilder; -import org.keycloak.events.EventType; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; -import org.keycloak.models.utils.FormMessage; -import org.keycloak.services.ServicesLogger; -import org.keycloak.services.messages.Messages; -import org.keycloak.sessions.AuthenticationSessionModel; +import org.sunbird.keycloak.utils.Constants; +import org.sunbird.keycloak.utils.HttpClient; /** * Created by joris on 11/11/2016. @@ -65,14 +55,18 @@ public void authenticate(AuthenticationFlowContext context) { mobileNumber = mobileNumberCreds.get(0); } + Map otpResponse = generateOtp(context); if (StringUtils.isNotBlank(mobileNumber) || StringUtils.isNotBlank(userEmail)) { - if (StringUtils.isNotBlank(userEmail)) { - logger.debug("KeycloakSmsAuthenticator@authenticate - Sending Email - " + userEmail); - sendEmail(context); - } + logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); if (StringUtils.isNotBlank(mobileNumber)) { - logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); - sendSMS(context, mobileNumber); + sendSMS(otpResponse, context, mobileNumber); + } + if (StringUtils.isNotBlank(userEmail)) { + logger.debug( + "KeycloakSmsAuthenticator@authenticate - Sending Email via sunbird - " + userEmail); + logger.debug("KeycloakSmsAuthenticator@authenticate - realmName - " + + context.getRealm().getDisplayName()); + sendEmailViaSunbird(otpResponse, context, userEmail); } } else { // The mobile number is NOT configured --> complain @@ -83,103 +77,70 @@ public void authenticate(AuthenticationFlowContext context) { } } - private void sendSMS(AuthenticationFlowContext context, String mobileNumber) { - // The mobile number is configured --> send an SMS - long nrOfDigits = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_LENGTH, 8L); - logger.debug("Using nrOfDigits " + nrOfDigits); - - logger.debug("KeycloakSmsAuthenticator@sendSMS"); - - long ttl = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_TTL, 10 * 60L); // 10 minutes in s + private Map generateOtp(AuthenticationFlowContext context) { + // The mobile number is configured --> send an SMS + long nrOfDigits = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), + KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_LENGTH, 8L); + logger.debug("Using nrOfDigits " + nrOfDigits); - logger.debug("Using ttl " + ttl + " (s)"); + logger.debug("KeycloakSmsAuthenticator@sendSMS"); - String code = KeycloakSmsAuthenticatorUtil.getSmsCode(nrOfDigits); + long ttl = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), + KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_TTL, 10 * 60L); // 10 minutes in s - storeSMSCode(context, code, new Date().getTime() + (ttl * 1000)); // s --> ms - if (KeycloakSmsAuthenticatorUtil.sendSmsCode(mobileNumber, code, context.getAuthenticatorConfig())) { - Response challenge = context.form().createForm("sms-validation.ftl"); - context.challenge(challenge); - } else { - Response challenge = context.form() - .setError("SMS could not be sent.") - .createForm("sms-validation-error.ftl"); - context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR, challenge); - } + logger.debug("Using ttl " + ttl + " (s)"); + String code = KeycloakSmsAuthenticatorUtil.getSmsCode(nrOfDigits); + storeSMSCode(context, code, new Date().getTime() + (ttl * 1000)); // s --> ms + Map response = new HashMap<>(); + response.put(Constants.OTP, code); + response.put(Constants.TTL, (ttl / 60)); + return response; } - - private void sendEmail(AuthenticationFlowContext context) { - logger.debug("KeycloakSmsAuthenticator@sendEmail"); - - UserModel user = context.getUser(); - AuthenticationSessionModel authenticationSession = context.getAuthenticationSession(); - String username = authenticationSession.getAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME); - - // we don't want people guessing usernames, so if there was a problem obtaining the user, the user will be null. - // just reset login for with a success message - if (user == null) { - context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); - return; - } - - String actionTokenUserId = authenticationSession.getAuthNote(DefaultActionTokenKey.ACTION_TOKEN_USER_ID); - if (actionTokenUserId != null && Objects.equals(user.getId(), actionTokenUserId)) { - logger.debugf("Forget-password triggered when reauthenticating user after authentication via action token. Skipping " + CREDENTIAL_TYPE + " screen and using user '%s' ", user.getUsername()); - context.success(); - return; - } - - - EventBuilder event = context.getEvent(); - // we don't want people guessing usernames, so if there is a problem, just continuously challenge - if (user.getEmail() == null || user.getEmail().trim().length() == 0) { - event.user(user) - .detail(Details.USERNAME, username) - .error(Errors.INVALID_EMAIL); - - context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); - return; - } - - int validityInSecs = context.getRealm().getActionTokenGeneratedByUserLifespan(); - int absoluteExpirationInSecs = Time.currentTime() + validityInSecs; - - // We send the secret in the email in a link as a query param. - ResetCredentialsActionToken token = new ResetCredentialsActionToken(user.getId(), absoluteExpirationInSecs, authenticationSession.getId()); - String link = UriBuilder - .fromUri(context.getActionTokenUrl(token.serialize(context.getSession(), context.getRealm(), context.getUriInfo()))) - .build() - .toString(); - long expirationInMinutes = TimeUnit.SECONDS.toMinutes(validityInSecs); - try { - logger.debug("sendEmail - Reset Link : " + link); - - context.getSession().getProvider(EmailTemplateProvider.class).setRealm(context.getRealm()).setUser(user).sendPasswordReset(link, expirationInMinutes); - - event.clone().event(EventType.SEND_RESET_PASSWORD) - .user(user) - .detail(Details.USERNAME, username) - .detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, authenticationSession.getId()).success(); - context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT)); - - - Response challenge = context.form() - .createForm("password-reset-email.ftl"); - context.failureChallenge(AuthenticationFlowError.UNKNOWN_USER, challenge); - - } catch (EmailException e) { - event.clone().event(EventType.SEND_RESET_PASSWORD) - .detail(Details.USERNAME, username) - .user(user) - .error(Errors.EMAIL_SEND_FAILED); - ServicesLogger.LOGGER.failedToSendPwdResetEmail(e); - Response challenge = context.form() - .setError(Messages.EMAIL_SENT_ERROR) - .createErrorPage(); - context.failure(AuthenticationFlowError.INTERNAL_ERROR, challenge); - } + + private void sendSMS(Map otpResponse, AuthenticationFlowContext context, + String mobileNumber) { + if (KeycloakSmsAuthenticatorUtil.sendSmsCode(mobileNumber, + (String) otpResponse.get(Constants.OTP), context.getAuthenticatorConfig())) { + setEnterOTPPage(context, true); + } else { + setEnterOTPPage(context, false); + } } + private void setEnterOTPPage(AuthenticationFlowContext context, Boolean flag) { + if (flag) { + Response challenge = context.form().createForm("sms-validation.ftl"); + context.challenge(challenge); + } else { + Response challenge = + context.form().setError("OTP could not be sent.").createForm("sms-validation-error.ftl"); + context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR, challenge); + } + } + + private void sendEmailViaSunbird(Map otpResponse, + AuthenticationFlowContext context, String userEmail) { + List emails = new ArrayList<>(Arrays.asList(userEmail)); + otpResponse.put(Constants.RECIPIENT_EMAILS, emails); + otpResponse.put(Constants.SUBJECT, Constants.MAIL_SUBJECT); + otpResponse.put(Constants.REALM_NAME, context.getRealm().getDisplayName()); + otpResponse.put(Constants.EMAIL_TEMPLATE_TYPE, Constants.FORGOT_PASSWORD_EMAIL_TEMPLATE); + otpResponse.put(Constants.BODY, Constants.BODY); + + logger.debug("KeycloakSmsAuthenticator@sendEmailViaSunbird - Sending Email - " + userEmail); + Map request = new HashMap<>(); + request.put(Constants.REQUEST, otpResponse); + + HttpResponse response = HttpClient.post(request, + (System.getenv(Constants.SUNBIRD_LMS_BASE_URL) + Constants.SEND_NOTIFICATION_URI), + System.getenv(Constants.SUNBIRD_LMS_AUTHORIZATION)); + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode == 200) { + setEnterOTPPage(context, true); + } else { + setEnterOTPPage(context, false); + } + } @Override public void action(AuthenticationFlowContext context) { @@ -299,4 +260,4 @@ public void close() { logger.debug("close called ..."); } -} +} \ No newline at end of file From 337dcd958caa5817682cd83c24de6e6543cb0f36 Mon Sep 17 00:00:00 2001 From: bvinayakumar <36028332+bvinayakumar@users.noreply.github.com> Date: Tue, 27 Nov 2018 18:39:23 +0530 Subject: [PATCH 17/18] Update KeycloakSmsAuthenticator.java --- .../sms/KeycloakSmsAuthenticator.java | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java index 878e0470..0b7297ea 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/resetcredential/sms/KeycloakSmsAuthenticator.java @@ -55,17 +55,13 @@ public void authenticate(AuthenticationFlowContext context) { mobileNumber = mobileNumberCreds.get(0); } - Map otpResponse = generateOtp(context); if (StringUtils.isNotBlank(mobileNumber) || StringUtils.isNotBlank(userEmail)) { - logger.debug("KeycloakSmsAuthenticator@authenticate - Sending SMS - " + mobileNumber); + Map otpResponse = generateOTP(context); + if (StringUtils.isNotBlank(mobileNumber)) { sendSMS(otpResponse, context, mobileNumber); } if (StringUtils.isNotBlank(userEmail)) { - logger.debug( - "KeycloakSmsAuthenticator@authenticate - Sending Email via sunbird - " + userEmail); - logger.debug("KeycloakSmsAuthenticator@authenticate - realmName - " - + context.getRealm().getDisplayName()); sendEmailViaSunbird(otpResponse, context, userEmail); } } else { @@ -77,7 +73,7 @@ public void authenticate(AuthenticationFlowContext context) { } } - private Map generateOtp(AuthenticationFlowContext context) { + private Map generateOTP(AuthenticationFlowContext context) { // The mobile number is configured --> send an SMS long nrOfDigits = KeycloakSmsAuthenticatorUtil.getConfigLong(context.getAuthenticatorConfig(), KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CODE_LENGTH, 8L); @@ -99,49 +95,54 @@ private Map generateOtp(AuthenticationFlowContext context) { private void sendSMS(Map otpResponse, AuthenticationFlowContext context, String mobileNumber) { - if (KeycloakSmsAuthenticatorUtil.sendSmsCode(mobileNumber, + logger.debug("KeycloakSmsAuthenticator@sendSMS - Sending SMS"); + + if (KeycloakSmsAuthenticatorUtil.sendSmsCode(mobileNumber, (String) otpResponse.get(Constants.OTP), context.getAuthenticatorConfig())) { - setEnterOTPPage(context, true); + navigateToEnterOTPPage(context, true); } else { - setEnterOTPPage(context, false); + navigateToEnterOTPPage(context, false); } } - private void setEnterOTPPage(AuthenticationFlowContext context, Boolean flag) { - if (flag) { - Response challenge = context.form().createForm("sms-validation.ftl"); - context.challenge(challenge); - } else { - Response challenge = - context.form().setError("OTP could not be sent.").createForm("sms-validation-error.ftl"); - context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR, challenge); - } - } - private void sendEmailViaSunbird(Map otpResponse, AuthenticationFlowContext context, String userEmail) { + logger.debug("KeycloakSmsAuthenticator@sendEmailViaSunbird - Sending Email via Sunbird API"); + List emails = new ArrayList<>(Arrays.asList(userEmail)); + otpResponse.put(Constants.RECIPIENT_EMAILS, emails); otpResponse.put(Constants.SUBJECT, Constants.MAIL_SUBJECT); otpResponse.put(Constants.REALM_NAME, context.getRealm().getDisplayName()); otpResponse.put(Constants.EMAIL_TEMPLATE_TYPE, Constants.FORGOT_PASSWORD_EMAIL_TEMPLATE); otpResponse.put(Constants.BODY, Constants.BODY); - logger.debug("KeycloakSmsAuthenticator@sendEmailViaSunbird - Sending Email - " + userEmail); Map request = new HashMap<>(); request.put(Constants.REQUEST, otpResponse); HttpResponse response = HttpClient.post(request, (System.getenv(Constants.SUNBIRD_LMS_BASE_URL) + Constants.SEND_NOTIFICATION_URI), System.getenv(Constants.SUNBIRD_LMS_AUTHORIZATION)); + int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { - setEnterOTPPage(context, true); + navigateToEnterOTPPage(context, true); } else { - setEnterOTPPage(context, false); + navigateToEnterOTPPage(context, false); } } + private void navigateToEnterOTPPage(AuthenticationFlowContext context, Boolean flag) { + if (flag) { + Response challenge = context.form().createForm("sms-validation.ftl"); + context.challenge(challenge); + } else { + Response challenge = + context.form().setError("OTP could not be sent.").createForm("sms-validation-error.ftl"); + context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR, challenge); + } + } + @Override public void action(AuthenticationFlowContext context) { logger.debug("action called ... context = " + context); @@ -197,7 +198,7 @@ public void action(AuthenticationFlowContext context) { // Store the code + expiration time in a UserCredential. Keycloak will persist these in the DB. // When the code is validated on another node (in a clustered environment) the other nodes have access to it's values too. private void storeSMSCode(AuthenticationFlowContext context, String code, Long expiringAt) { - logger.debug("KeycloakSmsAuthenticator@storeSMSCode" + "User name = " + context.getUser().getUsername()); + logger.debug("KeycloakSmsAuthenticator@storeSMSCode called"); UserCredentialModel credentials = new UserCredentialModel(); credentials.setType(KeycloakSmsAuthenticatorConstants.USR_CRED_MDL_SMS_CODE); @@ -210,12 +211,10 @@ private void storeSMSCode(AuthenticationFlowContext context, String code, Long e context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), credentials); } - protected CODE_STATUS validateCode(AuthenticationFlowContext context) { - logger.debug("KeycloakSmsAuthenticator@validateCode"); + logger.debug("KeycloakSmsAuthenticator@validateCode called"); CODE_STATUS result = CODE_STATUS.INVALID; - logger.debug("validateCode called ... "); MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); String enteredCode = formData.getFirst(KeycloakSmsAuthenticatorConstants.ANSW_SMS_CODE); KeycloakSession session = context.getSession(); @@ -227,14 +226,14 @@ protected CODE_STATUS validateCode(AuthenticationFlowContext context) { /*CredentialModel expTimeString = (CredentialModel) timeCreds.get(0);*/ logger.debug("KeycloakSmsAuthenticator@validateCode " + "User name = " + context.getUser().getUsername()); - logger.debug("KeycloakSmsAuthenticator@validateCode " + "Expected code = " + expectedCode.getValue() + " entered code = " + enteredCode); + logger.debug("KeycloakSmsAuthenticator@validateCode " + "Expected code = " + expectedCode.getValue() + " entered code = " + enteredCode); if (expectedCode != null) { result = enteredCode.equals(expectedCode.getValue()) ? CODE_STATUS.VALID : CODE_STATUS.INVALID; } logger.debug("result : " + result); - logger.debug("KeycloakSmsAuthenticator@validateCode- Result -" + result); + logger.debug("KeycloakSmsAuthenticator@validateCode - Result -" + result); return result; } @@ -260,4 +259,4 @@ public void close() { logger.debug("close called ..."); } -} \ No newline at end of file +} From 58721f2b332cca535fb94560bf1dff4fe38f38da Mon Sep 17 00:00:00 2001 From: Amol Ghatol Date: Thu, 29 Nov 2018 17:36:02 +0530 Subject: [PATCH 18/18] Issue #SB-3853 chore: Update - OTP input label on forgot password screen. --- keycloak/sms-provider/templates/sms-validation.ftl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keycloak/sms-provider/templates/sms-validation.ftl b/keycloak/sms-provider/templates/sms-validation.ftl index e7d1124e..5783031d 100755 --- a/keycloak/sms-provider/templates/sms-validation.ftl +++ b/keycloak/sms-provider/templates/sms-validation.ftl @@ -18,7 +18,7 @@
- Enter the code we sent to your device + Please enter the OTP that has been sent to you