mirror of
https://repository.entgra.net/community/device-mgt-core.git
synced 2025-10-06 02:01:45 +00:00
OTP for enrollment with Mutual TLS
Fixes https://roadmap.entgra.net/issues/10093
This commit is contained in:
parent
1e252dd67f
commit
a603a69f3e
@ -134,6 +134,8 @@ public final class DeviceManagementConstants {
|
|||||||
public static final String LAST_NAME = "last-name";
|
public static final String LAST_NAME = "last-name";
|
||||||
public static final String TENANT_ADMIN_USERNAME = "tenant-admin-username";
|
public static final String TENANT_ADMIN_USERNAME = "tenant-admin-username";
|
||||||
public static final String TENANT_ADMIN_PASSWORD = "tenant-admin-password";
|
public static final String TENANT_ADMIN_PASSWORD = "tenant-admin-password";
|
||||||
|
|
||||||
|
public static final int OTP_DEFAULT_EXPIRY_SECONDS = 3600;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class EventServices {
|
public static final class EventServices {
|
||||||
|
|||||||
@ -33,6 +33,7 @@ public class QREnrollmentDetails {
|
|||||||
public void setCustomValues(Map<String, String> customValues) {
|
public void setCustomValues(Map<String, String> customValues) {
|
||||||
this.customValues = customValues;
|
this.customValues = customValues;
|
||||||
}
|
}
|
||||||
|
int tokenExpiry;
|
||||||
|
|
||||||
public String getOwnershipType() { return ownershipType; }
|
public String getOwnershipType() { return ownershipType; }
|
||||||
|
|
||||||
@ -45,4 +46,12 @@ public class QREnrollmentDetails {
|
|||||||
public String getEnrollmentMode() { return enrollmentMode; }
|
public String getEnrollmentMode() { return enrollmentMode; }
|
||||||
|
|
||||||
public void setEnrollmentMode(String enrollmentMode) { this.enrollmentMode = 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;
|
package io.entgra.device.mgt.core.device.mgt.common.otp.mgt;
|
||||||
|
|
||||||
public enum OTPEmailTypes {
|
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 OTPManagementException if error occurred whle verifying validity of the OPT
|
||||||
* @throws BadRequestException if found an null value for OTP
|
* @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
|
* Invalidate the OTP and send welcome mail
|
||||||
@ -59,8 +60,7 @@ public interface OTPManagementService {
|
|||||||
boolean hasEmailRegistered(String email, String emailDomain) throws OTPManagementException,
|
boolean hasEmailRegistered(String email, String emailDomain) throws OTPManagementException,
|
||||||
DeviceManagementException;
|
DeviceManagementException;
|
||||||
|
|
||||||
OneTimePinDTO generateOneTimePin(String email, String emailType, String userName, Object metaDataObj,
|
OneTimePinDTO generateOneTimePin(OneTimePinDTO oneTimePinData, boolean persistPin) throws OTPManagementException;
|
||||||
int tenantId, boolean persistPin) throws OTPManagementException;
|
|
||||||
|
|
||||||
OneTimePinDTO getRenewedOtpByEmailAndMailType(String email, String emailType) 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 io.entgra.device.mgt.core.device.mgt.core.otp.mgt.exception.OTPManagementDAOException;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
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.sql.*;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -51,7 +56,8 @@ public class GenericOTPManagementDAOImpl extends AbstractDAOImpl implements OTPM
|
|||||||
+ "META_INFO, "
|
+ "META_INFO, "
|
||||||
+ "CREATED_AT,"
|
+ "CREATED_AT,"
|
||||||
+ "TENANT_ID,"
|
+ "TENANT_ID,"
|
||||||
+ "USERNAME) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
+ "USERNAME, "
|
||||||
|
+ "EXPIRY_TIME) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
try {
|
try {
|
||||||
Connection conn = this.getDBConnection();
|
Connection conn = this.getDBConnection();
|
||||||
Calendar calendar = Calendar.getInstance();
|
Calendar calendar = Calendar.getInstance();
|
||||||
@ -65,6 +71,8 @@ public class GenericOTPManagementDAOImpl extends AbstractDAOImpl implements OTPM
|
|||||||
stmt.setTimestamp(5, timestamp);
|
stmt.setTimestamp(5, timestamp);
|
||||||
stmt.setInt(6, oneTimePinDTO.getTenantId());
|
stmt.setInt(6, oneTimePinDTO.getTenantId());
|
||||||
stmt.setString(7, oneTimePinDTO.getUsername());
|
stmt.setString(7, oneTimePinDTO.getUsername());
|
||||||
|
stmt.setInt(8, oneTimePinDTO.getExpiryTime() == 0
|
||||||
|
? DeviceManagementConstants.OTPProperties.OTP_DEFAULT_EXPIRY_SECONDS : oneTimePinDTO.getExpiryTime());
|
||||||
stmt.addBatch();
|
stmt.addBatch();
|
||||||
}
|
}
|
||||||
stmt.executeBatch();
|
stmt.executeBatch();
|
||||||
|
|||||||
@ -117,7 +117,8 @@ public class OTPManagementServiceImpl implements OTPManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OneTimePinDTO isValidOTP(String oneTimeToken) throws OTPManagementException, BadRequestException {
|
public OneTimePinDTO isValidOTP(String oneTimeToken, boolean requireRenewal) throws OTPManagementException,
|
||||||
|
BadRequestException {
|
||||||
if (StringUtils.isBlank(oneTimeToken)){
|
if (StringUtils.isBlank(oneTimeToken)){
|
||||||
String msg = "Received blank OTP to verify. OTP: " + oneTimeToken;
|
String msg = "Received blank OTP to verify. OTP: " + oneTimeToken;
|
||||||
log.error(msg);
|
log.error(msg);
|
||||||
@ -141,6 +142,7 @@ public class OTPManagementServiceImpl implements OTPManagementService {
|
|||||||
oneTimePinDTO.getCreatedAt().getTime() + oneTimePinDTO.getExpiryTime() * 1000L);
|
oneTimePinDTO.getCreatedAt().getTime() + oneTimePinDTO.getExpiryTime() * 1000L);
|
||||||
|
|
||||||
if (currentTimestamp.after(expiredTimestamp)) {
|
if (currentTimestamp.after(expiredTimestamp)) {
|
||||||
|
if (requireRenewal) {
|
||||||
String renewedOTP = UUID.randomUUID().toString();
|
String renewedOTP = UUID.randomUUID().toString();
|
||||||
renewOTP(oneTimePinDTO, renewedOTP);
|
renewOTP(oneTimePinDTO, renewedOTP);
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
@ -152,6 +154,7 @@ public class OTPManagementServiceImpl implements OTPManagementService {
|
|||||||
props.setProperty("email", oneTimePinDTO.getEmail());
|
props.setProperty("email", oneTimePinDTO.getEmail());
|
||||||
props.setProperty("type", oneTimePinDTO.getEmailType());
|
props.setProperty("type", oneTimePinDTO.getEmailType());
|
||||||
sendMail(props, oneTimePinDTO.getEmail(), DeviceManagementConstants.EmailAttributes.USER_VERIFY_TEMPLATE);
|
sendMail(props, oneTimePinDTO.getEmail(), DeviceManagementConstants.EmailAttributes.USER_VERIFY_TEMPLATE);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return oneTimePinDTO;
|
return oneTimePinDTO;
|
||||||
@ -234,8 +237,14 @@ public class OTPManagementServiceImpl implements OTPManagementService {
|
|||||||
for (String username : deviceEnrollmentInvitation.getUsernames()) {
|
for (String username : deviceEnrollmentInvitation.getUsernames()) {
|
||||||
String emailAddress = DeviceManagerUtil.getUserClaimValue(
|
String emailAddress = DeviceManagerUtil.getUserClaimValue(
|
||||||
username, DeviceManagementConstants.User.CLAIM_EMAIL_ADDRESS);
|
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);
|
oneTimePinDTOList.add(oneTimePinDTO);
|
||||||
props.setProperty("first-name", DeviceManagerUtil.
|
props.setProperty("first-name", DeviceManagerUtil.
|
||||||
getUserClaimValue(username, DeviceManagementConstants.User.CLAIM_FIRST_NAME));
|
getUserClaimValue(username, DeviceManagementConstants.User.CLAIM_FIRST_NAME));
|
||||||
@ -269,27 +278,17 @@ public class OTPManagementServiceImpl implements OTPManagementService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create One Time Token
|
* Create One Time Token
|
||||||
* @param email email
|
* @param oneTimePinDTO Data related to the one time pin
|
||||||
* @param emailType email type
|
|
||||||
* @param userName username
|
|
||||||
* @param metaDataObj meta data object
|
|
||||||
* @param tenantId tenant Id
|
|
||||||
* @return {@link OneTimePinDTO}
|
* @return {@link OneTimePinDTO}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public OneTimePinDTO generateOneTimePin(String email, String emailType, String userName, Object metaDataObj,
|
public OneTimePinDTO generateOneTimePin(OneTimePinDTO oneTimePinDTO, boolean persistPin) throws OTPManagementException {
|
||||||
int tenantId, boolean persistPin) throws OTPManagementException {
|
|
||||||
|
|
||||||
String otpValue = UUID.randomUUID().toString();
|
String otpValue = UUID.randomUUID().toString();
|
||||||
|
|
||||||
Gson gson = new Gson();
|
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.setMetaInfo(metaInfo);
|
||||||
oneTimePinDTO.setOtpToken(otpValue);
|
oneTimePinDTO.setOtpToken(otpValue);
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
|
|||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
public class BasicAuthAuthenticator implements WebappAuthenticator {
|
public class BasicAuthAuthenticator implements WebappAuthenticator {
|
||||||
|
|
||||||
@ -50,16 +51,24 @@ public class BasicAuthAuthenticator implements WebappAuthenticator {
|
|||||||
@Override
|
@Override
|
||||||
public boolean canHandle(Request request) {
|
public boolean canHandle(Request request) {
|
||||||
/*
|
/*
|
||||||
This is done to avoid every endpoint being able to use basic auth. Add the following to
|
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.
|
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>
|
<context-param>
|
||||||
<param-name>basicAuth</param-name>
|
<param-name>basicAuth</param-name>
|
||||||
<param-value>true</param-value>
|
<param-value>true</param-value>
|
||||||
</context-param>
|
</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)) {
|
if (!isAuthenticationSupported(request)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (request.getCoyoteRequest() == null || request.getCoyoteRequest().getMimeHeaders() == null) {
|
if (request.getCoyoteRequest() == null || request.getCoyoteRequest().getMimeHeaders() == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -75,6 +84,20 @@ public class BasicAuthAuthenticator implements WebappAuthenticator {
|
|||||||
return false;
|
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
|
@Override
|
||||||
public AuthenticationInfo authenticate(Request request, Response response) {
|
public AuthenticationInfo authenticate(Request request, Response response) {
|
||||||
AuthenticationInfo authenticationInfo = new AuthenticationInfo();
|
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
|
// 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.
|
// as the value of this header, the client certificate subject dn should be passed.
|
||||||
if (request.getHeader(PROXY_MUTUAL_AUTH_HEADER) != null) {
|
if (request.getHeader(PROXY_MUTUAL_AUTH_HEADER) != null) {
|
||||||
|
log.info("PROXY_MUTUAL_AUTH_HEADER " + request.getHeader(PROXY_MUTUAL_AUTH_HEADER));
|
||||||
CertificateResponse certificateResponse = AuthenticatorFrameworkDataHolder.getInstance().
|
CertificateResponse certificateResponse = AuthenticatorFrameworkDataHolder.getInstance().
|
||||||
getCertificateManagementService().verifySubjectDN(request.getHeader(PROXY_MUTUAL_AUTH_HEADER));
|
getCertificateManagementService().verifySubjectDN(request.getHeader(PROXY_MUTUAL_AUTH_HEADER));
|
||||||
|
log.info("clientCertificate" + certificateResponse.getSerialNumber());
|
||||||
|
log.info("clientCertificate" + certificateResponse.getCommonName());
|
||||||
authenticationInfo = checkCertificateResponse(certificateResponse);
|
authenticationInfo = checkCertificateResponse(certificateResponse);
|
||||||
|
log.info("username" + authenticationInfo.getUsername());
|
||||||
}
|
}
|
||||||
else if (request.getHeader(MUTUAL_AUTH_HEADER) != null) {
|
else if (request.getHeader(MUTUAL_AUTH_HEADER) != null) {
|
||||||
|
log.info("MUTUAL_AUTH_HEADER");
|
||||||
Object object = request.getAttribute(CLIENT_CERTIFICATE_ATTRIBUTE);
|
Object object = request.getAttribute(CLIENT_CERTIFICATE_ATTRIBUTE);
|
||||||
X509Certificate[] clientCertificate = null;
|
X509Certificate[] clientCertificate = null;
|
||||||
if (object instanceof X509Certificate[]) {
|
if (object instanceof X509Certificate[]) {
|
||||||
|
log.info("clientCertificate");
|
||||||
clientCertificate = (X509Certificate[]) request.
|
clientCertificate = (X509Certificate[]) request.
|
||||||
getAttribute(CLIENT_CERTIFICATE_ATTRIBUTE);
|
getAttribute(CLIENT_CERTIFICATE_ATTRIBUTE);
|
||||||
}
|
}
|
||||||
if (clientCertificate != null && clientCertificate[0] != null) {
|
if (clientCertificate != null && clientCertificate[0] != null) {
|
||||||
CertificateResponse certificateResponse = AuthenticatorFrameworkDataHolder.getInstance().
|
CertificateResponse certificateResponse = AuthenticatorFrameworkDataHolder.getInstance().
|
||||||
getCertificateManagementService().verifyPEMSignature(clientCertificate[0]);
|
getCertificateManagementService().verifyPEMSignature(clientCertificate[0]);
|
||||||
|
log.info("clientCertificate" + certificateResponse.getSerialNumber());
|
||||||
|
log.info("clientCertificate" + certificateResponse.getCommonName());
|
||||||
authenticationInfo = checkCertificateResponse(certificateResponse);
|
authenticationInfo = checkCertificateResponse(certificateResponse);
|
||||||
|
log.info("username" + authenticationInfo.getUsername());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
authenticationInfo.setStatus(Status.FAILURE);
|
authenticationInfo.setStatus(Status.FAILURE);
|
||||||
|
|||||||
@ -49,8 +49,18 @@ public class OneTimeTokenAuthenticator implements WebappAuthenticator {
|
|||||||
try {
|
try {
|
||||||
OTPManagementService otpManagementService = AuthenticatorFrameworkDataHolder.getInstance()
|
OTPManagementService otpManagementService = AuthenticatorFrameworkDataHolder.getInstance()
|
||||||
.getOtpManagementService();
|
.getOtpManagementService();
|
||||||
OneTimePinDTO validOTP = otpManagementService.isValidOTP(request.getHeader(Constants.HTTPHeaders
|
OneTimePinDTO validOTP;
|
||||||
.ONE_TIME_TOKEN_HEADER));
|
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) {
|
if (validOTP != null) {
|
||||||
authenticationInfo.setStatus(Status.CONTINUE);
|
authenticationInfo.setStatus(Status.CONTINUE);
|
||||||
authenticationInfo.setTenantId(validOTP.getTenantId());
|
authenticationInfo.setTenantId(validOTP.getTenantId());
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user