Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Email OTP based verification at registration #720

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2220615
Merge pull request #1 from wso2-extensions/master
Gastro-Diron Jun 12, 2023
f4eda00
Moved the changes to a branch created on master
Gastro-Diron Jun 12, 2023
4ee00db
removed verificationMethod variable and duplicate methods
Gastro-Diron Jun 13, 2023
622a203
Update UserSelfRegistrationHandler.java
Gastro-Diron Jun 13, 2023
f51e3fb
seperated secretKey generation method to two methods
Gastro-Diron Jun 14, 2023
ceb74d3
Update Utils.java
Gastro-Diron Jun 15, 2023
13f5ff7
Assigned constant for OTPCode
Gastro-Diron Jun 19, 2023
4c4231c
Merge pull request #2 from Gastro-Diron/gastrojune14
Gastro-Diron Jun 19, 2023
c4b0c29
add unit test
Gastro-Diron Jun 22, 2023
1230ecc
Merge pull request #3 from Gastro-Diron/gastrojune22
Gastro-Diron Jun 28, 2023
7447dd7
Changes made after code review
Gastro-Diron Jun 29, 2023
ec66550
Reverted the changes in the resendcodeapiserviceimpl.java
Gastro-Diron Jun 29, 2023
ee2dc42
Revert the changes made in the resendcodeapiserviceimpl.java
Gastro-Diron Jun 29, 2023
5329407
change the visibility of triggerNotification to private
Gastro-Diron Jun 29, 2023
b4bca7c
Added the checkstyle to UserSelfRegistrationHandler.java
Gastro-Diron Jun 30, 2023
2d932b9
Added CheckStyle for Utils.java, IdentityRecoveryConstants.java, Self…
Gastro-Diron Jun 30, 2023
fa79de0
Mask the username
Gastro-Diron Jun 30, 2023
553c761
Revert the checkstyle
Gastro-Diron Jun 30, 2023
7d410d6
Change the description of the configuration
Gastro-Diron Jul 6, 2023
d4efc7c
Add checkstyle
Gastro-Diron Jul 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class IdentityRecoveryConstants {
public static final String NOTIFICATION_TYPE_RESEND_ADMIN_FORCED_PASSWORD_RESET_WITH_OTP =
"resendAdminForcedPasswordResetWithOTP";
public static final String NOTIFICATION_TYPE_ACCOUNT_CONFIRM = "accountconfirmation";
public static final String NOTIFICATION_TYPE_ACCOUNT_CONFIRM_EMAIL_OTP = "accountConfirmationEmailOTP";
public static final String NOTIFICATION_TYPE_RESEND_ACCOUNT_CONFIRM = "resendaccountconfirmation";
public static final String NOTIFICATION_TYPE_EMAIL_CONFIRM = "emailconfirm";
public static final String NOTIFICATION_TYPE_LITE_USER_EMAIL_CONFIRM = "liteUserEmailConfirmation";
Expand All @@ -71,6 +72,7 @@ public class IdentityRecoveryConstants {
public static final String TEMPLATE_TYPE = "TEMPLATE_TYPE";
public static final String EMAIL_TEMPLATE_NAME = "templateName";
public static final String CONFIRMATION_CODE = "confirmation-code";
public static final String EMAIL_OTP_CODE = "OTPCode";
public static final String VERIFICATION_PENDING_EMAIL = "verification-pending-email";
public static final String NEW_EMAIL_ADDRESS = "new-email-address";
public static final String NOTIFY = "notify";
Expand Down Expand Up @@ -163,6 +165,7 @@ public class IdentityRecoveryConstants {
public static final String USER_ACCOUNT_RECOVERY = "UAR";

public static final int SMS_OTP_CODE_LENGTH = 6;
public static final int EMAIL_OTP_CODE_LENGTH = 6;
public static final String ENABLE_DETAILED_ERROR_RESPONSE = "Recovery.ErrorMessage.EnableDetailedErrorMessages";
// Recovery code given at the username and password recovery initiation.
public static final int RECOVERY_CODE_DEFAULT_EXPIRY_TIME = 1;
Expand Down Expand Up @@ -537,6 +540,7 @@ public static class ConnectorConfig {
".Password.ReCaptcha.MaxFailedAttempts";
public static final String RECOVERY_CALLBACK_REGEX = "Recovery.CallbackRegex";
public static final String ENABLE_SELF_SIGNUP = "SelfRegistration.Enable";
public static final String ENABLE_EMAIL_OTP_VERIFICATION = "SelfRegistration.EmailOTPVerification.Enable";
Gastro-Diron marked this conversation as resolved.
Show resolved Hide resolved
public static final String ACCOUNT_LOCK_ON_CREATION = "SelfRegistration.LockOnCreation";
public static final String SEND_CONFIRMATION_NOTIFICATION = "SelfRegistration.SendConfirmationOnCreation";
public static final String SIGN_UP_NOTIFICATION_INTERNALLY_MANAGE = "SelfRegistration.Notification" +
Expand Down Expand Up @@ -606,6 +610,9 @@ public static class ConnectorConfig {
public static final String SELF_REGISTRATION_AUTO_LOGIN_ALIAS_NAME = "SelfRegistration.AutoLogin.AliasName";
}

/**
* This class contains the database queries.
*/
public static class SQLQueries {

public static final String STORE_RECOVERY_DATA = "INSERT INTO IDN_RECOVERY_DATA "
Expand All @@ -628,8 +635,8 @@ public static class SQLQueries {
"AND SCENARIO <> 'EMAIL_VERIFICATION_ON_UPDATE' AND SCENARIO <> 'MOBILE_VERIFICATION_ON_UPDATE'";

public static final String INVALIDATE_USER_CODES_CASE_INSENSITIVE =
"DELETE FROM IDN_RECOVERY_DATA WHERE LOWER(USER_NAME) = LOWER(?) AND USER_DOMAIN = ? AND TENANT_ID =? " +
"AND SCENARIO <> 'EMAIL_VERIFICATION_ON_UPDATE' AND SCENARIO <> 'MOBILE_VERIFICATION_ON_UPDATE'";
"DELETE FROM IDN_RECOVERY_DATA WHERE LOWER(USER_NAME) = LOWER(?) AND USER_DOMAIN = ? AND TENANT_ID =? AND SCENARIO <> 'EMAIL_VERIFICATION_ON_UPDATE' " +
Gastro-Diron marked this conversation as resolved.
Show resolved Hide resolved
"AND SCENARIO <> 'MOBILE_VERIFICATION_ON_UPDATE'";

public static final String INVALIDATE_USER_CODE_BY_SCENARIO = "DELETE FROM IDN_RECOVERY_DATA WHERE " +
"USER_NAME = ? AND SCENARIO = ? AND STEP = ? AND USER_DOMAIN = ? AND TENANT_ID =?";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ public Map<String, String> getPropertyNameMapping() {

Map<String, String> nameMapping = new HashMap<>();
nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.ENABLE_SELF_SIGNUP, "User self registration");
nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.ENABLE_EMAIL_OTP_VERIFICATION,
"Email OTP based verification");
nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.ACCOUNT_LOCK_ON_CREATION,
"Lock user account on creation");
nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.SEND_CONFIRMATION_NOTIFICATION,
Expand All @@ -94,7 +96,8 @@ public Map<String, String> getPropertyNameMapping() {
nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.SELF_REGISTRATION_RE_CAPTCHA, "Prompt reCaptcha");
nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.SELF_REGISTRATION_VERIFICATION_CODE_EXPIRY_TIME,
"User self registration verification link expiry time");
nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.SELF_REGISTRATION_SMSOTP_VERIFICATION_CODE_EXPIRY_TIME,
nameMapping.put(
IdentityRecoveryConstants.ConnectorConfig.SELF_REGISTRATION_SMSOTP_VERIFICATION_CODE_EXPIRY_TIME,
"User self registration SMS OTP expiry time");
nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.SELF_REGISTRATION_SMS_OTP_REGEX,
"User self registration SMS OTP regex");
Expand All @@ -118,6 +121,8 @@ public Map<String, String> getPropertyDescriptionMapping() {
Map<String, String> descriptionMapping = new HashMap<>();
descriptionMapping.put(IdentityRecoveryConstants.ConnectorConfig.ENABLE_SELF_SIGNUP,
"Allow user's to self register to the system.");
descriptionMapping.put(IdentityRecoveryConstants.ConnectorConfig.ENABLE_EMAIL_OTP_VERIFICATION,
"Enable email OTP-based verification during user registration.");
descriptionMapping.put(IdentityRecoveryConstants.ConnectorConfig.ACCOUNT_LOCK_ON_CREATION,
"Lock self registered user account until e-mail verification.");
descriptionMapping.put(IdentityRecoveryConstants.ConnectorConfig.SEND_CONFIRMATION_NOTIFICATION,
Expand Down Expand Up @@ -155,6 +160,7 @@ public String[] getPropertyNames() {

List<String> properties = new ArrayList<>();
properties.add(IdentityRecoveryConstants.ConnectorConfig.ENABLE_SELF_SIGNUP);
properties.add(IdentityRecoveryConstants.ConnectorConfig.ENABLE_EMAIL_OTP_VERIFICATION);
properties.add(IdentityRecoveryConstants.ConnectorConfig.ACCOUNT_LOCK_ON_CREATION);
properties.add(IdentityRecoveryConstants.ConnectorConfig.SEND_CONFIRMATION_NOTIFICATION);
properties.add(IdentityRecoveryConstants.ConnectorConfig.SIGN_UP_NOTIFICATION_INTERNALLY_MANAGE);
Expand All @@ -176,6 +182,7 @@ public String[] getPropertyNames() {
public Properties getDefaultPropertyValues(String tenantDomain) throws IdentityGovernanceException {

String enableSelfSignUp = "false";
String enableEmailOTPverification = "false";
String enableAccountLockOnCreation = "true";
String enableSendNotificationOnCreation = "false";
String enableNotificationInternallyManage = "true";
Expand All @@ -191,6 +198,8 @@ public Properties getDefaultPropertyValues(String tenantDomain) throws IdentityG

String selfSignUpProperty = IdentityUtil.getProperty(
IdentityRecoveryConstants.ConnectorConfig.ENABLE_SELF_SIGNUP);
String emailOTPverificationProprty = IdentityUtil.getProperty(
IdentityRecoveryConstants.ConnectorConfig.ENABLE_EMAIL_OTP_VERIFICATION);
String accountLockProperty = IdentityUtil.getProperty(
IdentityRecoveryConstants.ConnectorConfig.ACCOUNT_LOCK_ON_CREATION);
String sendNotificationOnCreationProperty = IdentityUtil.getProperty(
Expand Down Expand Up @@ -219,6 +228,9 @@ public Properties getDefaultPropertyValues(String tenantDomain) throws IdentityG
if (StringUtils.isNotEmpty(selfSignUpProperty)) {
enableSelfSignUp = selfSignUpProperty;
}
if (StringUtils.isNotEmpty(emailOTPverificationProprty)) {
enableEmailOTPverification = emailOTPverificationProprty;
}
if (StringUtils.isNotEmpty(accountLockProperty)) {
enableAccountLockOnCreation = accountLockProperty;
}
Expand Down Expand Up @@ -258,6 +270,8 @@ public Properties getDefaultPropertyValues(String tenantDomain) throws IdentityG

Map<String, String> defaultProperties = new HashMap<>();
defaultProperties.put(IdentityRecoveryConstants.ConnectorConfig.ENABLE_SELF_SIGNUP, enableSelfSignUp);
defaultProperties.put(IdentityRecoveryConstants.ConnectorConfig.ENABLE_EMAIL_OTP_VERIFICATION,
enableEmailOTPverification);
defaultProperties.put(IdentityRecoveryConstants.ConnectorConfig.ACCOUNT_LOCK_ON_CREATION,
enableAccountLockOnCreation);
defaultProperties.put(IdentityRecoveryConstants.ConnectorConfig.SEND_CONFIRMATION_NOTIFICATION,
Expand Down Expand Up @@ -311,6 +325,9 @@ public Map<String, Property> getMetaData() {
meta.put(IdentityRecoveryConstants.ConnectorConfig.ENABLE_SELF_SIGNUP,
getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue()));

meta.put(IdentityRecoveryConstants.ConnectorConfig.ENABLE_EMAIL_OTP_VERIFICATION,
getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue()));

meta.put(IdentityRecoveryConstants.ConnectorConfig.ACCOUNT_LOCK_ON_CREATION,
getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,20 @@
import org.wso2.carbon.identity.recovery.store.JDBCRecoveryDataStore;
import org.wso2.carbon.identity.recovery.store.UserRecoveryDataStore;
import org.wso2.carbon.identity.recovery.util.Utils;
import org.wso2.carbon.registry.core.utils.UUIDGenerator;
import org.wso2.carbon.user.core.UserCoreConstants;
import org.wso2.carbon.user.core.UserStoreException;
import org.wso2.carbon.user.core.UserStoreManager;

import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* This handler class is used to self register a user.
*/
public class UserSelfRegistrationHandler extends AbstractEventHandler {

private static final Log log = LogFactory.getLog(UserSelfRegistrationHandler.class);
Expand All @@ -77,10 +78,13 @@ public void handleEvent(Event event) throws IdentityEventException {

Map<String, Object> eventProperties = event.getEventProperties();
String userName = (String) eventProperties.get(IdentityEventConstants.EventProperty.USER_NAME);
UserStoreManager userStoreManager = (UserStoreManager) eventProperties.get(IdentityEventConstants.EventProperty.USER_STORE_MANAGER);
UserStoreManager userStoreManager = (UserStoreManager) eventProperties.get(
IdentityEventConstants.EventProperty.USER_STORE_MANAGER);

String tenantDomain = (String) eventProperties.get(IdentityEventConstants.EventProperty.TENANT_DOMAIN);
String domainName = userStoreManager.getRealmConfiguration().getUserStoreProperty(UserCoreConstants.RealmConfig.PROPERTY_DOMAIN_NAME);
String tenantDomain = (String) eventProperties.get(
IdentityEventConstants.EventProperty.TENANT_DOMAIN);
String domainName = userStoreManager.getRealmConfiguration().getUserStoreProperty(
UserCoreConstants.RealmConfig.PROPERTY_DOMAIN_NAME);

String[] roleList = (String[]) eventProperties.get(IdentityEventConstants.EventProperty.ROLE_LIST);

Expand Down Expand Up @@ -118,7 +122,8 @@ public void handleEvent(Event event) throws IdentityEventException {
(IdentityRecoveryConstants.ConnectorConfig.SEND_CONFIRMATION_NOTIFICATION, user.getTenantDomain()));

boolean isNotificationInternallyManage = Boolean.parseBoolean(Utils.getConnectorConfig
(IdentityRecoveryConstants.ConnectorConfig.SIGN_UP_NOTIFICATION_INTERNALLY_MANAGE, user.getTenantDomain()));
(IdentityRecoveryConstants.ConnectorConfig.SIGN_UP_NOTIFICATION_INTERNALLY_MANAGE,
user.getTenantDomain()));

if (IdentityEventConstants.Event.POST_ADD_USER.equals(event.getEventName())) {
UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance();
Expand All @@ -137,7 +142,8 @@ public void handleEvent(Event event) throws IdentityEventException {
}

boolean isSelfRegistrationConfirmationNotify = Boolean.parseBoolean(Utils.getSignUpConfigs
(IdentityRecoveryConstants.ConnectorConfig.SELF_REGISTRATION_NOTIFY_ACCOUNT_CONFIRMATION, user.getTenantDomain()));
(IdentityRecoveryConstants.ConnectorConfig.SELF_REGISTRATION_NOTIFY_ACCOUNT_CONFIRMATION,
user.getTenantDomain()));

// If notify confirmation is enabled and both iAccountLockOnCreation &&
// EnableConfirmationOnCreation are disabled then send account creation notification.
Expand Down Expand Up @@ -407,8 +413,8 @@ protected void triggerNotification(User user, String type, String code, Property
try {
IdentityRecoveryServiceDataHolder.getInstance().getIdentityEventService().handleEvent(identityMgtEvent);
} catch (IdentityEventException e) {
throw Utils.handleServerException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_TRIGGER_NOTIFICATION, user
.getUserName(), e);
throw Utils.handleServerException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_TRIGGER_NOTIFICATION,
user.getUserName(), e);
}
}

Expand All @@ -425,8 +431,18 @@ protected void triggerNotification(User user, String type, String code, Property
private void triggerNotification(User user, String notificationChannel, String code, Property[] props,
String eventName) throws IdentityRecoveryException {

boolean emailOTPenabled = false;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take this logic out of triggernotification

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we need to take this out of the TriggerNotification method then we need to pass this as an additional parameter to the method. If that is OK, I can take this out of the method and add an additional parameter for this method.

try {
emailOTPenabled = Boolean.parseBoolean(Utils.getConnectorConfig(
IdentityRecoveryConstants.ConnectorConfig.ENABLE_EMAIL_OTP_VERIFICATION, user.getTenantDomain()));
} catch (IdentityEventException e) {
throw Utils.handleServerException(
IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_ERROR_GETTING_CONNECTOR_CONFIG,
user.getTenantDomain(), e);
}

if (log.isDebugEnabled()) {
log.debug("Sending self user registration notification user: " + user.getUserName());
log.debug("Sending account confirmation notification to user: " + user.getUserName());
Gastro-Diron marked this conversation as resolved.
Show resolved Hide resolved
}
HashMap<String, Object> properties = new HashMap<>();
properties.put(IdentityEventConstants.EventProperty.USER_NAME, user.getUserName());
Expand All @@ -440,10 +456,19 @@ private void triggerNotification(User user, String notificationChannel, String c
}
}
if (StringUtils.isNotBlank(code)) {
properties.put(IdentityRecoveryConstants.CONFIRMATION_CODE, code);
if (emailOTPenabled) {
properties.put(IdentityRecoveryConstants.EMAIL_OTP_CODE, code);
} else {
properties.put(IdentityRecoveryConstants.CONFIRMATION_CODE, code);
}
}
properties.put(IdentityRecoveryConstants.TEMPLATE_TYPE,
if (emailOTPenabled) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check for the canhandle method to which events it can handle

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method is used only for the self user registration purpose. So we do not have to remove this logic out of TriggerNotification method.

properties.put(IdentityRecoveryConstants.TEMPLATE_TYPE,
IdentityRecoveryConstants.NOTIFICATION_TYPE_ACCOUNT_CONFIRM_EMAIL_OTP);
} else {
properties.put(IdentityRecoveryConstants.TEMPLATE_TYPE,
IdentityRecoveryConstants.NOTIFICATION_TYPE_ACCOUNT_CONFIRM);
}
Event identityMgtEvent = new Event(eventName, properties);
try {
IdentityRecoveryServiceDataHolder.getInstance().getIdentityEventService().handleEvent(identityMgtEvent);
Expand Down
Loading
Loading