Skip to content

Commit

Permalink
Refactor config reading, add throttling
Browse files Browse the repository at this point in the history
  • Loading branch information
nipunthathsara committed Aug 25, 2021
1 parent f270114 commit 39bad28
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 78 deletions.
Expand Up @@ -49,8 +49,6 @@

import java.io.IOException;
import java.util.HashMap;

import java.util.Properties;
import java.util.UUID;

/**
Expand Down Expand Up @@ -90,9 +88,14 @@ public GenerationResponseDTO generateSMSOTP(String userId) throws SMSOTPExceptio
throw Utils.handleClientException(Constants.ErrorMessage.CLIENT_INVALID_USER_ID, userId);
}

// If throttling is enabled, check if the resend request has sent too early.
boolean resendThrottlingEnabled = SMSOTPServiceDataHolder.getConfigs().isResendThrottlingEnabled();
if (resendThrottlingEnabled) {
shouldThrottle(userId);
}

// Retrieve mobile number if notifications are managed internally.
boolean sendNotification = Boolean.parseBoolean(
Utils.readConfigurations().getProperty(Constants.SMS_OTP_TRIGGER_NOTIFICATION));
boolean sendNotification = SMSOTPServiceDataHolder.getConfigs().isTriggerNotification();
String mobileNumber = null;
if (sendNotification) {
mobileNumber = getMobileNumber(user.getUsername(), userStoreManager);
Expand Down Expand Up @@ -123,9 +126,7 @@ public ValidationResponseDTO validateSMSOTP(String transactionId, String userId,
Constants.ErrorMessage.CLIENT_MANDATORY_VALIDATION_PARAMETERS_EMPTY, missingParam);
}

// Should the reason be exposed upon a failed OTP validation.
boolean showFailureReason = Boolean.parseBoolean(
StringUtils.trim(Utils.readConfigurations().getProperty(Constants.SMS_OTP_SHOW_FAILURE_REASON)));
boolean showFailureReason = SMSOTPServiceDataHolder.getConfigs().isShowFailureReason();

// Retrieve session from the database.
String sessionId = String.valueOf(userId.hashCode());
Expand Down Expand Up @@ -181,33 +182,18 @@ private ValidationResponseDTO isValid(SessionDTO sessionDTO, String smsOTP, Stri

private SessionDTO issueOTP(String mobileNumber, User user) throws SMSOTPException {

// Read server configurations.
Properties properties = Utils.readConfigurations();
String otpValidityPeriodValue =
StringUtils.trim(properties.getProperty(Constants.SMS_OTP_TOKEN_VALIDITY_PERIOD));
String otpRenewIntervalValue = StringUtils.trim(properties.getProperty(Constants.SMS_OTP_TOKEN_RENEW_INTERVAL));
// Notification sending, defaults to false.
boolean triggerNotification =
StringUtils.isNotBlank(properties.getProperty(Constants.SMS_OTP_TRIGGER_NOTIFICATION)) &&
Boolean.parseBoolean(properties.getProperty(Constants.SMS_OTP_TRIGGER_NOTIFICATION));
// If not defined, use the default values.
int otpValidityPeriod = StringUtils.isNumeric(otpValidityPeriodValue) ?
Integer.parseInt(otpValidityPeriodValue) : Constants.DEFAULT_SMS_OTP_VALIDITY_PERIOD;
// If not defined, defaults to zero to renew always.
int otpRenewalInterval = StringUtils.isNumeric(otpRenewIntervalValue) ?
Integer.parseInt(otpRenewIntervalValue) : 0;
// Should we send the same OTP when asked to resend.
boolean resendSameOtpEnabled = otpRenewalInterval > 0 && otpRenewalInterval < otpValidityPeriod;

// If 'resending same OTP' is enabled, check if such exists.
boolean triggerNotification = SMSOTPServiceDataHolder.getConfigs().isTriggerNotification();
boolean resendSameOtp = SMSOTPServiceDataHolder.getConfigs().isResendSameOtp();

// If 'Resend same OTP' is enabled, check if such OTP exists.
SessionDTO sessionDTO = null;
if (resendSameOtpEnabled) {
sessionDTO = getPreviousValidSession(user, otpRenewalInterval);
if (resendSameOtp) {
sessionDTO = getPreviousValidSession(user);
}

// If no such valid OTPs exist, generate a new OTP and proceed.
if (sessionDTO == null) {
sessionDTO = generateNewOTP(user, otpValidityPeriod);
sessionDTO = generateNewOTP(user);
}

// Sending SMS notifications.
Expand All @@ -217,28 +203,24 @@ private SessionDTO issueOTP(String mobileNumber, User user) throws SMSOTPExcepti
return sessionDTO;
}

private SessionDTO generateNewOTP(User user, int otpExpiryTime) throws SMSOTPServerException {

// Read server configs.
Properties properties = Utils.readConfigurations();
boolean isAlphaNumericOtpEnabled = Boolean.parseBoolean(
properties.getProperty(Constants.SMS_OTP_ALPHANUMERIC_TOKEN_ENABLED));
String otpLengthValue = StringUtils.trim(properties.getProperty(Constants.SMS_OTP_TOKEN_LENGTH));
private SessionDTO generateNewOTP(User user) throws SMSOTPServerException {

int otpLength = StringUtils.isNumeric(otpLengthValue) ?
Integer.parseInt(otpLengthValue) : Constants.DEFAULT_OTP_LENGTH;
boolean isAlphaNumericOtpEnabled = SMSOTPServiceDataHolder.getConfigs().isAlphaNumericOTP();
int otpLength = SMSOTPServiceDataHolder.getConfigs().getOtpLength();
int otpValidityPeriod = SMSOTPServiceDataHolder.getConfigs().getOtpValidityPeriod();

// Generate OTP.
String otp = OneTimePasswordUtils.generateOTP(
UUID.randomUUID().toString(),
String.valueOf(Constants.NUMBER_BASE),
otpLength,
isAlphaNumericOtpEnabled);

// Save the otp in the 'IDN_AUTH_SESSION_STORE' table.
SessionDTO sessionDTO = new SessionDTO();
sessionDTO.setOtp(otp);
sessionDTO.setGeneratedTime(System.currentTimeMillis());
sessionDTO.setExpiryTime(otpExpiryTime);
sessionDTO.setExpiryTime(otpValidityPeriod);
sessionDTO.setFullQualifiedUserName(user.getFullQualifiedUsername());
sessionDTO.setUserId(user.getUserID());
String jsonString;
Expand Down Expand Up @@ -303,7 +285,7 @@ private String getMobileNumber(String username, UserStoreManager userStoreManage
return mobileNumber;
}

private SessionDTO getPreviousValidSession(User user, int otpRenewalInterval) throws SMSOTPException {
private SessionDTO getPreviousValidSession(User user) throws SMSOTPException {

// Search previous session object.
String sessionId = String.valueOf(user.getUserID().hashCode());
Expand All @@ -315,15 +297,41 @@ private SessionDTO getPreviousValidSession(User user, int otpRenewalInterval) th
}
return null;
}
SessionDTO previousSessionDTO;
SessionDTO previousOTPSessionDTO;
try {
previousSessionDTO = new ObjectMapper().readValue(jsonString, SessionDTO.class);
previousOTPSessionDTO = new ObjectMapper().readValue(jsonString, SessionDTO.class);
} catch (IOException e) {
throw Utils.handleServerException(Constants.ErrorMessage.SERVER_JSON_SESSION_MAPPER_ERROR, null, e);
}
// If the previous OTP is issued within the interval, return the same.
return (System.currentTimeMillis() - previousSessionDTO.getGeneratedTime() < otpRenewalInterval) ?
previousSessionDTO : null;
int otpRenewalInterval = SMSOTPServiceDataHolder.getConfigs().getOtpRenewalInterval();
return (System.currentTimeMillis() - previousOTPSessionDTO.getGeneratedTime() < otpRenewalInterval) ?
previousOTPSessionDTO : null;
}

private void shouldThrottle(String userId) throws SMSOTPException {

String sessionId = String.valueOf(userId.hashCode());
String jsonString = (String) SessionDataStore.getInstance().
getSessionData(sessionId, Constants.SESSION_TYPE_OTP);
if (StringUtils.isBlank(jsonString)) {
return;
}

SessionDTO previousOTPSessionDTO;
try {
previousOTPSessionDTO = new ObjectMapper().readValue(jsonString, SessionDTO.class);
} catch (IOException e) {
throw Utils.handleServerException(Constants.ErrorMessage.SERVER_JSON_SESSION_MAPPER_ERROR, null, e);
}

long elapsedTimeSinceLastOtp = System.currentTimeMillis() - previousOTPSessionDTO.getGeneratedTime();
int resendThrottleInterval = SMSOTPServiceDataHolder.getConfigs().getResendThrottleInterval();
if (elapsedTimeSinceLastOtp < resendThrottleInterval) {
long waitingPeriod = (resendThrottleInterval - elapsedTimeSinceLastOtp) / 1000;
throw Utils.handleClientException(
Constants.ErrorMessage.CLIENT_SLOW_DOWN_RESEND, String.valueOf(waitingPeriod));
}
}

private int getTenantId() {
Expand Down
Expand Up @@ -33,6 +33,7 @@ public class Constants {
public static final int NUMBER_BASE = 2;
public static final int DEFAULT_OTP_LENGTH = 6;
public static final int DEFAULT_SMS_OTP_VALIDITY_PERIOD = 60000;
public static final int DEFAULT_RESEND_THROTTLE_INTERVAL = 1800000;
public static final String SMS_OTP_NOTIFICATION_TEMPLATE = "sendOTP";

public static final String SMS_OTP_IDENTITY_EVENT_MODULE_NAME = "smsOtp";
Expand All @@ -41,7 +42,8 @@ public class Constants {
public static final String SMS_OTP_TOKEN_VALIDITY_PERIOD = "smsOtp.tokenValidityPeriod";
public static final String SMS_OTP_ALPHANUMERIC_TOKEN_ENABLED = "smsOtp.isEnableAlphanumericToken";
public static final String SMS_OTP_TRIGGER_NOTIFICATION = "smsOtp.triggerNotification";
public static final String SMS_OTP_TOKEN_RENEW_INTERVAL = "smsOtp.tokenRenewInterval";
public static final String SMS_OTP_TOKEN_RENEWAL_INTERVAL = "smsOtp.tokenRenewalInterval";
public static final String SMS_OTP_RESEND_THROTTLE_INTERVAL = "smsOtp.resendThrottleInterval";
public static final String SMS_OTP_SHOW_FAILURE_REASON = "smsOtp.showValidationFailureReason";

/**
Expand All @@ -63,6 +65,8 @@ public enum ErrorMessage {
"Provided OTP is invalid. User id : %s."),
CLIENT_NO_OTP_FOR_USER("SMS-60009", "No OTP fround for the user.",
"No OTP found for the user Id : %s."),
CLIENT_SLOW_DOWN_RESEND("SMS-60010", "Slow down.",
"Please wait %s seconds before resend."),

// Server error codes.
SERVER_USER_STORE_MANAGER_ERROR("SMS-65001", "User store manager error.",
Expand Down
@@ -0,0 +1,154 @@
/*
* Copyright (c) 2021, WSO2 Inc. (http://www.wso2.com).
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.wso2.carbon.identity.smsotp.common.dto;

/**
* This class holds the SMS OTP feature configurations.
*/
public class ConfigsDTO {

private boolean isEnabled;
private boolean showFailureReason;
private boolean isAlphaNumericOTP;
private boolean triggerNotification;
private boolean resendSameOtp;
private boolean resendThrottlingEnabled;
private int otpLength;
private int otpValidityPeriod;
private int otpRenewalInterval;
private int resendThrottleInterval;

public boolean isEnabled() {

return isEnabled;
}

public void setEnabled(boolean enabled) {

isEnabled = enabled;
}

public boolean isShowFailureReason() {

return showFailureReason;
}

public void setShowFailureReason(boolean showFailureReason) {

this.showFailureReason = showFailureReason;
}

public boolean isAlphaNumericOTP() {

return isAlphaNumericOTP;
}

public void setAlphaNumericOTP(boolean alphaNumericOTP) {

isAlphaNumericOTP = alphaNumericOTP;
}

public boolean isTriggerNotification() {

return triggerNotification;
}

public void setTriggerNotification(boolean triggerNotification) {

this.triggerNotification = triggerNotification;
}

public boolean isResendSameOtp() {

return resendSameOtp;
}

public void setResendSameOtp(boolean resendSameOtp) {

this.resendSameOtp = resendSameOtp;
}

public boolean isResendThrottlingEnabled() {

return resendThrottlingEnabled;
}

public void setResendThrottlingEnabled(boolean resendThrottlingEnabled) {

this.resendThrottlingEnabled = resendThrottlingEnabled;
}

public int getOtpLength() {

return otpLength;
}

public void setOtpLength(int otpLength) {

this.otpLength = otpLength;
}

public int getOtpValidityPeriod() {

return otpValidityPeriod;
}

public void setOtpValidityPeriod(int otpValidityPeriod) {

this.otpValidityPeriod = otpValidityPeriod;
}

public int getOtpRenewalInterval() {

return otpRenewalInterval;
}

public void setOtpRenewalInterval(int otpRenewalInterval) {

this.otpRenewalInterval = otpRenewalInterval;
}

public int getResendThrottleInterval() {

return resendThrottleInterval;
}

public void setResendThrottleInterval(int resendThrottleInterval) {

this.resendThrottleInterval = resendThrottleInterval;
}

@Override
public String toString() {

StringBuilder sb = new StringBuilder("ConfigsDTO {");
sb.append("\n\tisEnabled = ").append(isEnabled)
.append(",\n\tshowFailureReason = ").append(showFailureReason)
.append(",\n\tisAlphaNumericOTP = ").append(isAlphaNumericOTP)
.append(",\n\ttriggerNotification = ").append(triggerNotification)
.append(",\n\tresendSameOtp = ").append(resendSameOtp)
.append(",\n\tresendThrottlingEnabled = ").append(resendThrottlingEnabled)
.append(",\n\totpLength = ").append(otpLength)
.append(",\n\totpValidityPeriod = ").append(otpValidityPeriod)
.append(",\n\totpRenewalInterval = ").append(otpRenewalInterval)
.append(",\n\tresendThrottleInterval = ").append(resendThrottleInterval)
.append("\n}");
return sb.toString();
}
}
Expand Up @@ -24,7 +24,6 @@
public class GenerationResponseDTO {

private String smsOTP;
private String transactionId;

public String getSmsOTP() {

Expand All @@ -35,14 +34,4 @@ public void setSmsOTP(String smsOTP) {

this.smsOTP = smsOTP;
}

public String getTransactionId() {

return transactionId;
}

public void setTransactionId(String transactionId) {

this.transactionId = transactionId;
}
}

0 comments on commit 39bad28

Please sign in to comment.