OTP for enrollment with Mutual TLS

Fixes https://roadmap.entgra.net/issues/10093
remotes/1728937285237262521/master
inoshperera 1 year ago committed by pasindu
parent 1e252dd67f
commit a603a69f3e

@ -134,6 +134,8 @@ public final class DeviceManagementConstants {
public static final String LAST_NAME = "last-name";
public static final String TENANT_ADMIN_USERNAME = "tenant-admin-username";
public static final String TENANT_ADMIN_PASSWORD = "tenant-admin-password";
public static final int OTP_DEFAULT_EXPIRY_SECONDS = 3600;
}
public static final class EventServices {

@ -33,6 +33,7 @@ public class QREnrollmentDetails {
public void setCustomValues(Map<String, String> customValues) {
this.customValues = customValues;
}
int tokenExpiry;
public String getOwnershipType() { return ownershipType; }
@ -45,4 +46,12 @@ public class QREnrollmentDetails {
public String getEnrollmentMode() { return enrollmentMode; }
public void setEnrollmentMode(String enrollmentMode) { this.enrollmentMode = enrollmentMode; }
public int getTokenExpiry() {
return tokenExpiry;
}
public void setTokenExpiry(int tokenExpiry) {
this.tokenExpiry = tokenExpiry;
}
}

@ -19,5 +19,5 @@
package io.entgra.device.mgt.core.device.mgt.common.otp.mgt;
public enum OTPEmailTypes {
USER_VERIFY, DEVICE_ENROLLMENT
USER_VERIFY, DEVICE_ENROLLMENT, USER_INVITE
}

@ -35,7 +35,8 @@ public interface OTPManagementService {
* @throws OTPManagementException if error occurred whle verifying validity of the OPT
* @throws BadRequestException if found an null value for OTP
*/
OneTimePinDTO isValidOTP(String oneTimeToken) throws OTPManagementException, BadRequestException;
OneTimePinDTO isValidOTP(String oneTimeToken, boolean requireRenewal) throws
OTPManagementException, BadRequestException;
/**
* Invalidate the OTP and send welcome mail
@ -59,8 +60,7 @@ public interface OTPManagementService {
boolean hasEmailRegistered(String email, String emailDomain) throws OTPManagementException,
DeviceManagementException;
OneTimePinDTO generateOneTimePin(String email, String emailType, String userName, Object metaDataObj,
int tenantId, boolean persistPin) throws OTPManagementException;
OneTimePinDTO generateOneTimePin(OneTimePinDTO oneTimePinData, boolean persistPin) throws OTPManagementException;
OneTimePinDTO getRenewedOtpByEmailAndMailType(String email, String emailType) throws OTPManagementException;

@ -25,7 +25,12 @@ import io.entgra.device.mgt.core.device.mgt.core.otp.mgt.dao.OTPManagementDAO;
import io.entgra.device.mgt.core.device.mgt.core.otp.mgt.exception.OTPManagementDAOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.DeviceManagementConstants;
import org.wso2.carbon.device.mgt.common.exceptions.DBConnectionException;
import org.wso2.carbon.device.mgt.common.otp.mgt.dto.OneTimePinDTO;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.AbstractDAOImpl;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.OTPManagementDAO;
import org.wso2.carbon.device.mgt.core.otp.mgt.exception.OTPManagementDAOException;
import java.sql.*;
import java.util.Calendar;
import java.util.List;
@ -51,7 +56,8 @@ public class GenericOTPManagementDAOImpl extends AbstractDAOImpl implements OTPM
+ "META_INFO, "
+ "CREATED_AT,"
+ "TENANT_ID,"
+ "USERNAME) VALUES (?, ?, ?, ?, ?, ?, ?)";
+ "USERNAME, "
+ "EXPIRY_TIME) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
try {
Connection conn = this.getDBConnection();
Calendar calendar = Calendar.getInstance();
@ -65,6 +71,8 @@ public class GenericOTPManagementDAOImpl extends AbstractDAOImpl implements OTPM
stmt.setTimestamp(5, timestamp);
stmt.setInt(6, oneTimePinDTO.getTenantId());
stmt.setString(7, oneTimePinDTO.getUsername());
stmt.setInt(8, oneTimePinDTO.getExpiryTime() == 0
? DeviceManagementConstants.OTPProperties.OTP_DEFAULT_EXPIRY_SECONDS : oneTimePinDTO.getExpiryTime());
stmt.addBatch();
}
stmt.executeBatch();

@ -117,7 +117,8 @@ public class OTPManagementServiceImpl implements OTPManagementService {
}
@Override
public OneTimePinDTO isValidOTP(String oneTimeToken) throws OTPManagementException, BadRequestException {
public OneTimePinDTO isValidOTP(String oneTimeToken, boolean requireRenewal) throws OTPManagementException,
BadRequestException {
if (StringUtils.isBlank(oneTimeToken)){
String msg = "Received blank OTP to verify. OTP: " + oneTimeToken;
log.error(msg);
@ -141,6 +142,7 @@ public class OTPManagementServiceImpl implements OTPManagementService {
oneTimePinDTO.getCreatedAt().getTime() + oneTimePinDTO.getExpiryTime() * 1000L);
if (currentTimestamp.after(expiredTimestamp)) {
if (requireRenewal) {
String renewedOTP = UUID.randomUUID().toString();
renewOTP(oneTimePinDTO, renewedOTP);
Gson gson = new Gson();
@ -152,6 +154,7 @@ public class OTPManagementServiceImpl implements OTPManagementService {
props.setProperty("email", oneTimePinDTO.getEmail());
props.setProperty("type", oneTimePinDTO.getEmailType());
sendMail(props, oneTimePinDTO.getEmail(), DeviceManagementConstants.EmailAttributes.USER_VERIFY_TEMPLATE);
}
return null;
}
return oneTimePinDTO;
@ -234,8 +237,14 @@ public class OTPManagementServiceImpl implements OTPManagementService {
for (String username : deviceEnrollmentInvitation.getUsernames()) {
String emailAddress = DeviceManagerUtil.getUserClaimValue(
username, DeviceManagementConstants.User.CLAIM_EMAIL_ADDRESS);
oneTimePinDTO = generateOneTimePin(emailAddress, OTPEmailTypes.DEVICE_ENROLLMENT.toString(), username,
null, tenantId, false);
OneTimePinDTO oneTimePinData = new OneTimePinDTO();
oneTimePinData.setEmail(emailAddress);
oneTimePinData.setTenantId(tenantId);
oneTimePinData.setUsername(username);
oneTimePinData.setEmailType(OTPEmailTypes.USER_INVITE.toString());
oneTimePinDTO = generateOneTimePin(oneTimePinData, false);
oneTimePinDTOList.add(oneTimePinDTO);
props.setProperty("first-name", DeviceManagerUtil.
getUserClaimValue(username, DeviceManagementConstants.User.CLAIM_FIRST_NAME));
@ -269,27 +278,17 @@ public class OTPManagementServiceImpl implements OTPManagementService {
/**
* Create One Time Token
* @param email email
* @param emailType email type
* @param userName username
* @param metaDataObj meta data object
* @param tenantId tenant Id
* @param oneTimePinDTO Data related to the one time pin
* @return {@link OneTimePinDTO}
*/
@Override
public OneTimePinDTO generateOneTimePin(String email, String emailType, String userName, Object metaDataObj,
int tenantId, boolean persistPin) throws OTPManagementException {
public OneTimePinDTO generateOneTimePin(OneTimePinDTO oneTimePinDTO, boolean persistPin) throws OTPManagementException {
String otpValue = UUID.randomUUID().toString();
Gson gson = new Gson();
String metaInfo = gson.toJson(metaDataObj);
String metaInfo = gson.toJson(oneTimePinDTO.getMetaInfo());
OneTimePinDTO oneTimePinDTO = new OneTimePinDTO();
oneTimePinDTO.setEmail(email);
oneTimePinDTO.setTenantId(tenantId);
oneTimePinDTO.setUsername(userName);
oneTimePinDTO.setEmailType(emailType);
oneTimePinDTO.setMetaInfo(metaInfo);
oneTimePinDTO.setOtpToken(otpValue);

@ -35,6 +35,7 @@ import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
import java.nio.charset.Charset;
import java.util.Base64;
import java.util.Properties;
import java.util.StringTokenizer;
public class BasicAuthAuthenticator implements WebappAuthenticator {
@ -50,16 +51,24 @@ public class BasicAuthAuthenticator implements WebappAuthenticator {
@Override
public boolean canHandle(Request request) {
/*
This is done to avoid every endpoint being able to use basic auth. Add the following to
the required web.xml of the web app.
This is done to avoid every web app being able to use basic auth. Add the following to
the required web.xml of the web app. This is a global config for a web app to allow all
contexts of the web app to use basic auth
<context-param>
<param-name>basicAuth</param-name>
<param-value>true</param-value>
</context-param>
Adding the basicAuthAllowList parameter allows to selectively allow some context paths in a
web app to use basic auth while all the other context remain unavailable with basic auth.
If this parameter is present, any context that requires basic auth must be specially
added as comma separated list to the param-value of basicAuthAllowList.
*/
if (!isAllowListedForBasicAuth(request)) {
if (!isAuthenticationSupported(request)) {
return false;
}
}
if (request.getCoyoteRequest() == null || request.getCoyoteRequest().getMimeHeaders() == null) {
return false;
}
@ -75,6 +84,20 @@ public class BasicAuthAuthenticator implements WebappAuthenticator {
return false;
}
private boolean isAllowListedForBasicAuth(Request request) {
String param = request.getContext().findParameter("basicAuthAllowList");
if (param != null && !param.isEmpty()) {
//Add the nonSecured end-points to cache
String[] basicAuthAllowList = param.split(",");
for (String contexPath : basicAuthAllowList) {
if (request.getRequestURI().toString().endsWith(contexPath.trim())) {
return true;
}
}
}
return false;
}
@Override
public AuthenticationInfo authenticate(Request request, Response response) {
AuthenticationInfo authenticationInfo = new AuthenticationInfo();

@ -74,21 +74,30 @@ public class CertificateAuthenticator implements WebappAuthenticator {
// When there is a load balancer terminating mutual SSL, it should pass this header along and
// as the value of this header, the client certificate subject dn should be passed.
if (request.getHeader(PROXY_MUTUAL_AUTH_HEADER) != null) {
log.info("PROXY_MUTUAL_AUTH_HEADER " + request.getHeader(PROXY_MUTUAL_AUTH_HEADER));
CertificateResponse certificateResponse = AuthenticatorFrameworkDataHolder.getInstance().
getCertificateManagementService().verifySubjectDN(request.getHeader(PROXY_MUTUAL_AUTH_HEADER));
log.info("clientCertificate" + certificateResponse.getSerialNumber());
log.info("clientCertificate" + certificateResponse.getCommonName());
authenticationInfo = checkCertificateResponse(certificateResponse);
log.info("username" + authenticationInfo.getUsername());
}
else if (request.getHeader(MUTUAL_AUTH_HEADER) != null) {
log.info("MUTUAL_AUTH_HEADER");
Object object = request.getAttribute(CLIENT_CERTIFICATE_ATTRIBUTE);
X509Certificate[] clientCertificate = null;
if (object instanceof X509Certificate[]) {
log.info("clientCertificate");
clientCertificate = (X509Certificate[]) request.
getAttribute(CLIENT_CERTIFICATE_ATTRIBUTE);
}
if (clientCertificate != null && clientCertificate[0] != null) {
CertificateResponse certificateResponse = AuthenticatorFrameworkDataHolder.getInstance().
getCertificateManagementService().verifyPEMSignature(clientCertificate[0]);
log.info("clientCertificate" + certificateResponse.getSerialNumber());
log.info("clientCertificate" + certificateResponse.getCommonName());
authenticationInfo = checkCertificateResponse(certificateResponse);
log.info("username" + authenticationInfo.getUsername());
} else {
authenticationInfo.setStatus(Status.FAILURE);

@ -49,8 +49,18 @@ public class OneTimeTokenAuthenticator implements WebappAuthenticator {
try {
OTPManagementService otpManagementService = AuthenticatorFrameworkDataHolder.getInstance()
.getOtpManagementService();
OneTimePinDTO validOTP = otpManagementService.isValidOTP(request.getHeader(Constants.HTTPHeaders
.ONE_TIME_TOKEN_HEADER));
OneTimePinDTO validOTP;
if (request.getRequestURI().toString().endsWith("cloud/download-url")
|| request.getRequestURI().toString().endsWith("cloud/tenant")) {
validOTP = otpManagementService.isValidOTP(request.getHeader(Constants.HTTPHeaders
.ONE_TIME_TOKEN_HEADER), true);
} else {
log.info("Validating OTP for enrollments PIN: " + request.getHeader(Constants
.HTTPHeaders.ONE_TIME_TOKEN_HEADER));
validOTP = otpManagementService.isValidOTP(request.getHeader(Constants.HTTPHeaders
.ONE_TIME_TOKEN_HEADER), false);
}
if (validOTP != null) {
authenticationInfo.setStatus(Status.CONTINUE);
authenticationInfo.setTenantId(validOTP.getTenantId());

Loading…
Cancel
Save