Add OTP managing functionality

revert-70ac1926
tcdlpds@gmail.com 4 years ago
parent 0c2a7fe0dd
commit 709a7ab592

@ -35,7 +35,6 @@
package org.wso2.carbon.device.mgt.jaxrs.service.api;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Info;
import io.swagger.annotations.ExtensionProperty;
@ -50,6 +49,7 @@ import io.swagger.annotations.ResponseHeader;
import org.apache.axis2.transport.http.HTTPConstants;
import org.wso2.carbon.apimgt.annotations.api.Scopes;
import org.wso2.carbon.apimgt.annotations.api.Scope;
import org.wso2.carbon.device.mgt.common.otp.mgt.wrapper.OTPMailWrapper;
import org.wso2.carbon.device.mgt.jaxrs.beans.ActivityList;
import org.wso2.carbon.device.mgt.jaxrs.beans.BasicUserInfo;
import org.wso2.carbon.device.mgt.jaxrs.beans.BasicUserInfoList;
@ -1221,4 +1221,50 @@ public interface UserManagementService {
response = ErrorResponse.class)
})
Response getPermissionsOfUser();
@POST
@Path("/one-time-pin")
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",
value = "Getting the permission details of the current user",
notes = "A user may granted more than one permission in IoTS. Using this REST API "
+ "you can get the permission/permission the current user has granted. ",
tags = "User Management",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = Constants.SCOPE, value = "perm:user:permission-view")
})
}
)
@ApiResponses(value = {
@ApiResponse(
code = 200,
message = "OK. \n Successfully fetched the list of permissions the user "
+ "has granted.",
response = PermissionList.class,
responseHeaders = {
@ResponseHeader(
name = "Content-Type",
description = "The content type of the body"),
@ResponseHeader(
name = "ETag",
description = "Entity Tag of the response resource.\n" +
"Used by caches, or in conditional requests."),
@ResponseHeader(
name = "Last-Modified",
description = "Date and time the resource was last modified.\n" +
"Used by caches, or in conditional requests."),
}),
@ApiResponse(
code = 404,
message = "Not Found. \n The specified resource does not exist.\n",
response = ErrorResponse.class),
@ApiResponse(
code = 500,
message = "Internal Server Error. \n Server error occurred while fetching the "
+ "list of roles assigned to the specified user.",
response = ErrorResponse.class)
})
Response sendEmailVerifyingMail(OTPMailWrapper otpMailWrapper);
}

@ -45,8 +45,11 @@ import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.EnrolmentInfo;
import org.wso2.carbon.device.mgt.common.configuration.mgt.ConfigurationManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.OTPManagementException;
import org.wso2.carbon.device.mgt.common.operation.mgt.Activity;
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException;
import org.wso2.carbon.device.mgt.common.spi.OTPManagementService;
import org.wso2.carbon.device.mgt.common.otp.mgt.wrapper.OTPMailWrapper;
import org.wso2.carbon.device.mgt.core.DeviceManagementConstants;
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService;
import org.wso2.carbon.device.mgt.core.service.EmailMetaInfo;
@ -1115,6 +1118,53 @@ public class UserManagementServiceImpl implements UserManagementService {
}
}
/**
* Method used to send an invitation email to a existing user to enroll a device.
*
* @param otpMailWrapper Username list of the users to be invited
*/
@POST
@Path("/one-time-pin")
@Produces({MediaType.APPLICATION_JSON})
public Response sendEmailVerifyingMail(OTPMailWrapper otpMailWrapper) {
if (log.isDebugEnabled()) {
log.debug("Sending enrollment invitation mail to existing user.");
}
DeviceManagementProviderService dms = DeviceMgtAPIUtils.getDeviceManagementService();
OTPManagementService oms = DeviceMgtAPIUtils.getOTPManagementService();
try {
String otpToken = oms.createOTPToken(otpMailWrapper);
Properties props = new Properties();
props.setProperty("first-name", otpMailWrapper.getFirstName());
props.setProperty("otp-token", otpToken);
EmailMetaInfo metaInfo = new EmailMetaInfo(otpMailWrapper.getEmail(), props);
dms.sendEnrolmentInvitation(DeviceManagementConstants.EmailAttributes.USER_VERIFY_TEMPLATE,
metaInfo);
} catch (DeviceManagementException e) {
String msg = "Error occurred while inviting user to enrol their device";
if (e.getMessage() != null && !e.getMessage().isEmpty()) {
msg = e.getMessage();
}
log.error(msg, e);
return Response.serverError().entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build();
} catch (ConfigurationManagementException e) {
String msg = "Error occurred while sending the email invitations. Mail server not configured.";
return Response.serverError().entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build();
} catch (OTPManagementException e) {
String msg = "Error occurred while generating and storing the OTP data";
log.error(msg, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
} catch (org.wso2.carbon.device.mgt.common.exceptions.BadRequestException e) {
String msg = "Bad Request : Found invalid request payload to create OTP toke.";
log.error(msg, e);
return Response.status(Response.Status.BAD_REQUEST).entity(msg).build();
}
return Response.status(Response.Status.OK).entity("Invitation mails have been sent.").build();
}
private Map<String, String> buildDefaultUserClaims(String firstName, String lastName, String emailAddress,
boolean isFresh) {
Map<String, String> defaultUserClaims = new HashMap<>();

@ -52,6 +52,7 @@ import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
import org.wso2.carbon.device.mgt.common.permission.mgt.PermissionManagerService;
import org.wso2.carbon.device.mgt.common.report.mgt.ReportManagementService;
import org.wso2.carbon.device.mgt.common.spi.DeviceTypeGeneratorService;
import org.wso2.carbon.device.mgt.common.spi.OTPManagementService;
import org.wso2.carbon.device.mgt.core.app.mgt.ApplicationManagementProviderService;
import org.wso2.carbon.device.mgt.core.device.details.mgt.DeviceInformationManager;
import org.wso2.carbon.device.mgt.core.dto.DeviceTypeVersion;
@ -134,13 +135,14 @@ public class DeviceMgtAPIUtils {
public static final String DAS_ADMIN_SERVICE_EP = "https://" + DAS_HOST_NAME + ":" + DAS_PORT + "/services/";
private static SSLContext sslContext;
private static Log log = LogFactory.getLog(DeviceMgtAPIUtils.class);
private static final Log log = LogFactory.getLog(DeviceMgtAPIUtils.class);
private static KeyStore keyStore;
private static KeyStore trustStore;
private static char[] keyStorePassword;
private static IntegrationClientService integrationClientService;
private static MetadataManagementService metadataManagementService;
private static volatile OTPManagementService otpManagementService;
static {
String keyStorePassword = ServerConfiguration.getInstance().getFirstProperty("Security.KeyStore.Password");
@ -459,6 +461,29 @@ public class DeviceMgtAPIUtils {
return metadataManagementService;
}
/**
* Initializing and accessing method for OTPManagementService.
*
* @return OTPManagementService instance
* @throws IllegalStateException if OTPManagementService cannot be initialized
*/
public static OTPManagementService getOTPManagementService() {
if (otpManagementService == null) {
synchronized (DeviceMgtAPIUtils.class) {
if (otpManagementService == null) {
PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext();
otpManagementService = (OTPManagementService) ctx.getOSGiService(OTPManagementService.class, null);
if (otpManagementService == null) {
String msg = "OTP Management service not initialized.";
log.error(msg);
throw new IllegalStateException(msg);
}
}
}
}
return otpManagementService;
}
/**
* Method for initializing ReportManagementService
* @return ReportManagementServie Instance

@ -48,7 +48,8 @@
<context-param>
<param-name>nonSecuredEndPoints</param-name>
<param-value>
/api/device-mgt/v1.0/users/validate
/api/device-mgt/v1.0/users/validate,
/api/device-mgt/v1.0/users/one-time-pin,
</param-value>
</context-param>

@ -0,0 +1,33 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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.device.mgt.common.exceptions;
/**
* Exception thrown due to Database Connection issues.
*/
public class DBConnectionException extends Exception {
private static final long serialVersionUID = -6779125067467878014L;
public DBConnectionException(String message, Throwable cause) {
super(message, cause);
}
public DBConnectionException(String msg) {
super(msg);
}
}

@ -0,0 +1,44 @@
/*
* Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* 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.device.mgt.common.exceptions;
public class OTPManagementException extends Exception {
private static final long serialVersionUID = 397485329551276175L;
public OTPManagementException(String msg, Exception nestedEx) {
super(msg, nestedEx);
}
public OTPManagementException(String message, Throwable cause) {
super(message, cause);
}
public OTPManagementException(String msg) {
super(msg);
}
public OTPManagementException() {
super();
}
public OTPManagementException(Throwable cause) {
super(cause);
}
}

@ -0,0 +1,104 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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.device.mgt.common.otp.mgt.dto;
import java.sql.Timestamp;
public class OTPMailDTO {
int id;
String otpToken;
String tenantDomain;
String email;
String emailType;
String metaInfo;
Timestamp createdAt;
int expiryTime;
boolean isExpired;
boolean isTenantCreated;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getOtpToken() {
return otpToken;
}
public void setOtpToken(String otpToken) {
this.otpToken = otpToken;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getEmailType() {
return emailType;
}
public void setEmailType(String emailType) {
this.emailType = emailType;
}
public String getMetaInfo() { return metaInfo; }
public void setMetaInfo(String metaInfo) {
this.metaInfo = metaInfo;
}
public Timestamp getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Timestamp createdAt) {
this.createdAt = createdAt;
}
public int getExpiryTime() {
return expiryTime;
}
public void setExpiryTime(int expiryTime) {
this.expiryTime = expiryTime;
}
public boolean isExpired() {
return isExpired;
}
public void setExpired(boolean expired) {
isExpired = expired;
}
public String getTenantDomain() { return tenantDomain; }
public void setTenantDomain(String tenantDomain) { this.tenantDomain = tenantDomain; }
public boolean isTenantCreated() { return isTenantCreated; }
public void setTenantCreated(boolean tenantCreated) { isTenantCreated = tenantCreated; }
}

@ -0,0 +1,84 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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.device.mgt.common.otp.mgt.wrapper;
public class OTPMailWrapper {
private String firstName;
private String lastName;
private String tenantDomain;
private String adminUsername;
private String adminPassword;
private String email;
private String emailType;
public String getTenantDomain() {
return tenantDomain;
}
public void setTenantDomain(String tenantDomain) {
this.tenantDomain = tenantDomain;
}
public String getAdminUsername() {
return adminUsername;
}
public void setAdminUsername(String adminUsername) {
this.adminUsername = adminUsername;
}
public String getAdminPassword() {
return adminPassword;
}
public void setAdminPassword(String adminPassword) {
this.adminPassword = adminPassword;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getEmailType() {
return emailType;
}
public void setEmailType(String emailType) {
this.emailType = emailType;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}

@ -0,0 +1,34 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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.device.mgt.common.spi;
import org.wso2.carbon.device.mgt.common.exceptions.BadRequestException;
import org.wso2.carbon.device.mgt.common.exceptions.OTPManagementException;
import org.wso2.carbon.device.mgt.common.otp.mgt.wrapper.OTPMailWrapper;
public interface OTPManagementService {
/**
* Cretae OTP token and store tenant details in the DB
* @param otpMailWrapper OTP Mail Wrapper object which contains tenant details of registering user
* @return OTPToken
* @throws OTPManagementException if error occurs while creating OTP token and storing tenant details.
* @throws BadRequestException if found and incompatible payload to create OTP token.
*/
String createOTPToken (OTPMailWrapper otpMailWrapper) throws OTPManagementException, BadRequestException;
}

@ -116,6 +116,7 @@ public final class DeviceManagementConstants {
public static final String USER_REGISTRATION_TEMPLATE = "user-registration";
public static final String USER_ENROLLMENT_TEMPLATE = "user-enrollment";
public static final String USER_VERIFY_TEMPLATE = "user-verify";
public static final String DEFAULT_ENROLLMENT_TEMPLATE = "default-enrollment-invitation";
}

@ -35,6 +35,7 @@ import org.wso2.carbon.device.mgt.common.permission.mgt.PermissionManagerService
import org.wso2.carbon.device.mgt.common.report.mgt.ReportManagementService;
import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService;
import org.wso2.carbon.device.mgt.common.spi.DeviceTypeGeneratorService;
import org.wso2.carbon.device.mgt.common.spi.OTPManagementService;
import org.wso2.carbon.device.mgt.core.DeviceManagementConstants;
import org.wso2.carbon.device.mgt.core.app.mgt.ApplicationManagementProviderService;
import org.wso2.carbon.device.mgt.core.app.mgt.ApplicationManagerProviderServiceImpl;
@ -56,6 +57,8 @@ import org.wso2.carbon.device.mgt.core.notification.mgt.NotificationManagementSe
import org.wso2.carbon.device.mgt.core.notification.mgt.dao.NotificationManagementDAOFactory;
import org.wso2.carbon.device.mgt.core.operation.mgt.OperationManagerImpl;
import org.wso2.carbon.device.mgt.core.operation.mgt.dao.OperationManagementDAOFactory;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.OTPManagementDAOFactory;
import org.wso2.carbon.device.mgt.core.otp.mgt.service.OTPManagementServiceImpl;
import org.wso2.carbon.device.mgt.core.permission.mgt.PermissionManagerServiceImpl;
import org.wso2.carbon.device.mgt.core.privacy.PrivacyComplianceProvider;
import org.wso2.carbon.device.mgt.core.privacy.impl.PrivacyComplianceProviderImpl;
@ -178,6 +181,7 @@ public class DeviceManagementServiceComponent {
NotificationManagementDAOFactory.init(dsConfig);
OperationManagementDAOFactory.init(dsConfig);
MetadataManagementDAOFactory.init(dsConfig);
OTPManagementDAOFactory.init(dsConfig.getJndiLookupDefinition().getJndiName());
/*Initialize the device cache*/
DeviceManagerUtil.initializeDeviceCache();
@ -330,6 +334,9 @@ public class DeviceManagementServiceComponent {
MetadataManagementService metadataManagementService = new MetadataManagementServiceImpl();
bundleContext.registerService(MetadataManagementService.class.getName(), metadataManagementService, null);
OTPManagementService otpManagementService = new OTPManagementServiceImpl();
bundleContext.registerService(OTPManagementService.class.getName(), otpManagementService, null);
/* Registering App Management service */
try {
AppManagementConfigurationManager.getInstance().initConfig();

@ -0,0 +1,33 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. 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.device.mgt.core.otp.mgt.dao;
import org.wso2.carbon.device.mgt.common.exceptions.DBConnectionException;
import org.wso2.carbon.device.mgt.core.otp.mgt.util.ConnectionManagerUtil;
import java.sql.Connection;
/**
* This class deals with getting the DB connection.
*/
public abstract class AbstractDAOImpl {
protected Connection getDBConnection() throws DBConnectionException {
return ConnectionManagerUtil.getDBConnection();
}
}

@ -0,0 +1,32 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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.device.mgt.core.otp.mgt.dao;
import org.wso2.carbon.device.mgt.common.otp.mgt.dto.OTPMailDTO;
import org.wso2.carbon.device.mgt.core.otp.mgt.exception.OTPManagementDAOException;
public interface OTPManagementDAO {
/**
* Save OTP token data and tenant details of registering user
* @param otpMailDTO OTPMailDTO
* @return Primary key of the newly adding data raw
* @throws OTPManagementDAOException if error occurred whule storing data
*/
int addOTPData(OTPMailDTO otpMailDTO) throws OTPManagementDAOException;
}

@ -0,0 +1,76 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. 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.device.mgt.core.otp.mgt.dao;
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.UnsupportedDatabaseEngineException;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.impl.GenericOTPManagementDAOImpl;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.impl.OracleOTPManagementDAOImpl;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.impl.PostgreSQLOTPManagementDAOImpl;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.impl.SQLServerOTPManagementDAOImpl;
import org.wso2.carbon.device.mgt.core.otp.mgt.util.ConnectionManagerUtil;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* This class intends to act as the primary entity that hides all DAO instantiation related complexities and logic so
* that the business objection handling layer doesn't need to be aware of the same providing seamless plug-ability of
* different data sources, connection acquisition mechanisms as well as different forms of DAO implementations to the
* high-level implementations that require Application management related metadata persistence.
*/
public class OTPManagementDAOFactory {
private static String databaseEngine;
private static final Log log = LogFactory.getLog(OTPManagementDAOFactory.class);
public static void init(String datasourceName) {
ConnectionManagerUtil.resolveDataSource(datasourceName);
databaseEngine = ConnectionManagerUtil.getDatabaseType();
}
public static void init(DataSource dtSource) {
try (Connection connection = dtSource.getConnection()) {
databaseEngine = connection.getMetaData().getDatabaseProductName();
} catch (SQLException e) {
log.error("Error occurred while retrieving config.datasource connection", e);
}
}
public static OTPManagementDAO getOTPManagementDAO() {
if (databaseEngine != null) {
switch (databaseEngine) {
case DeviceManagementConstants.DataBaseTypes.DB_TYPE_H2:
case DeviceManagementConstants.DataBaseTypes.DB_TYPE_MYSQL:
return new GenericOTPManagementDAOImpl();
case DeviceManagementConstants.DataBaseTypes.DB_TYPE_POSTGRESQL:
return new PostgreSQLOTPManagementDAOImpl();
case DeviceManagementConstants.DataBaseTypes.DB_TYPE_MSSQL:
return new SQLServerOTPManagementDAOImpl();
case DeviceManagementConstants.DataBaseTypes.DB_TYPE_ORACLE:
return new OracleOTPManagementDAOImpl();
default:
throw new UnsupportedDatabaseEngineException("Unsupported database engine : " + databaseEngine);
}
}
throw new IllegalStateException("Database engine has not initialized properly.");
}
}

@ -0,0 +1,85 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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.device.mgt.core.otp.mgt.dao.impl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.exceptions.DBConnectionException;
import org.wso2.carbon.device.mgt.common.otp.mgt.dto.OTPMailDTO;
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.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Calendar;
public class GenericOTPManagementDAOImpl extends AbstractDAOImpl implements OTPManagementDAO {
private static final Log log = LogFactory.getLog(GenericOTPManagementDAOImpl.class);
@Override
public int addOTPData(OTPMailDTO otpMailDTO) throws OTPManagementDAOException {
if (log.isDebugEnabled()) {
log.debug("Request received in DAO Layer to create an OTP data entry");
log.debug("OTP Details : ");
log.debug("OTP key : " + otpMailDTO.getOtpToken() + " Email : " + otpMailDTO.getEmail());
}
String sql = "INSERT INTO DM_OTP_DATA "
+ "(OTP_TOKEN, "
+ "TENANT_DOMAIN,"
+ "EMAIL, "
+ "EMAIL_TYPE, "
+ "META_INFO, "
+ "CREATED_AT) VALUES (?, ?, ?, ?, ?, ?)";
try {
Connection conn = this.getDBConnection();
Calendar calendar = Calendar.getInstance();
Timestamp timestamp = new Timestamp(calendar.getTime().getTime());
try (PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
stmt.setString(1, otpMailDTO.getOtpToken());
stmt.setString(2, otpMailDTO.getTenantDomain());
stmt.setString(3, otpMailDTO.getEmail());
stmt.setString(4, otpMailDTO.getEmailType());
stmt.setString(5, otpMailDTO.getMetaInfo());
stmt.setTimestamp(6, timestamp);
stmt.executeUpdate();
try (ResultSet rs = stmt.getGeneratedKeys()) {
if (rs.next()) {
return rs.getInt(1);
}
return -1;
}
}
} catch (DBConnectionException e) {
String msg = "Error occurred while obtaining the DB connection to create an opt entry for email "
+ otpMailDTO.getEmail();
log.error(msg, e);
throw new OTPManagementDAOException(msg, e);
} catch (SQLException e) {
String msg = "Error occurred while executing SQL to create an otp entry for email " + otpMailDTO.getEmail();
log.error(msg, e);
throw new OTPManagementDAOException(msg, e);
}
}
}

@ -0,0 +1,24 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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.device.mgt.core.otp.mgt.dao.impl;
/**
* This handles OTP managing DAO methods which are specific to Oracle.
*/
public class OracleOTPManagementDAOImpl extends GenericOTPManagementDAOImpl{
}

@ -0,0 +1,25 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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.device.mgt.core.otp.mgt.dao.impl;
/**
* This handles OTP managing DAO methods which are specific to PostgreSQL.
*/
public class PostgreSQLOTPManagementDAOImpl extends GenericOTPManagementDAOImpl{
}

@ -0,0 +1,24 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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.device.mgt.core.otp.mgt.dao.impl;
/**
* This handles OTP managing DAO methods which are specific to MSSQL.
*/
public class SQLServerOTPManagementDAOImpl extends GenericOTPManagementDAOImpl{
}

@ -0,0 +1,31 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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.device.mgt.core.otp.mgt.exception;
/**
* Exception thrown during the ApplicationDTO Management DAO operations.
*/
public class OTPManagementDAOException extends Exception {
public OTPManagementDAOException(String message, Throwable throwable) {
super(message, throwable);
}
public OTPManagementDAOException(String message) {
super(message, new Exception());
}
}

@ -0,0 +1,132 @@
/* Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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.device.mgt.core.otp.mgt.service;
import com.google.gson.Gson;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.exceptions.BadRequestException;
import org.wso2.carbon.device.mgt.common.exceptions.DBConnectionException;
import org.wso2.carbon.device.mgt.common.exceptions.OTPManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.TransactionManagementException;
import org.wso2.carbon.device.mgt.common.otp.mgt.dto.OTPMailDTO;
import org.wso2.carbon.device.mgt.common.spi.OTPManagementService;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.OTPManagementDAO;
import org.wso2.carbon.device.mgt.common.otp.mgt.wrapper.OTPMailWrapper;
import org.wso2.carbon.device.mgt.core.otp.mgt.dao.OTPManagementDAOFactory;
import org.wso2.carbon.device.mgt.core.otp.mgt.exception.OTPManagementDAOException;
import org.wso2.carbon.device.mgt.core.otp.mgt.util.ConnectionManagerUtil;
import java.util.UUID;
public class OTPManagementServiceImpl implements OTPManagementService {
private static final Log log = LogFactory.getLog(OTPManagementServiceImpl.class);
private OTPManagementDAO otpManagementDAO;
public OTPManagementServiceImpl() {
initDataAccessObjects();
}
private void initDataAccessObjects() {
otpManagementDAO = OTPManagementDAOFactory.getOTPManagementDAO();
}
@Override
public String createOTPToken(OTPMailWrapper otpMailWrapper) throws OTPManagementException, BadRequestException {
if (!isValidOTPTokenCreatingRequest(otpMailWrapper)){
String msg = "Found invalid payload with OTP creating request";
log.error(msg);
throw new BadRequestException(msg);
}
Gson gson = new Gson();
String metaInfo = gson.toJson(otpMailWrapper);
String otpValue = UUID.randomUUID().toString();
OTPMailDTO otpMailDTO = new OTPMailDTO();
otpMailDTO.setEmail(otpMailWrapper.getEmail());
otpMailDTO.setTenantDomain(otpMailWrapper.getTenantDomain());
otpMailDTO.setEmailType(otpMailWrapper.getEmailType());
otpMailDTO.setMetaInfo(metaInfo);
otpMailDTO.setOtpToken(otpValue);
try {
ConnectionManagerUtil.beginDBTransaction();
if (this.otpManagementDAO.addOTPData(otpMailDTO) == -1) {
ConnectionManagerUtil.rollbackDBTransaction();
String msg = "OTP data saving failed. Please, contact Administrator";
log.error(msg);
throw new OTPManagementException(msg);
}
ConnectionManagerUtil.commitDBTransaction();
return otpValue;
} catch (TransactionManagementException e) {
String msg = "Error occurred while disabling AutoCommit.";
log.error(msg, e);
throw new OTPManagementException(msg, e);
} catch (DBConnectionException e) {
String msg = "Error occurred while getting database connection.";
log.error(msg, e);
throw new OTPManagementException(msg, e);
} catch (OTPManagementDAOException e) {
ConnectionManagerUtil.rollbackDBTransaction();
String msg = "Error occurred while saving the OTP data. Email address: " + otpMailDTO.getEmail();
log.error(msg, e);
throw new OTPManagementException(msg, e);
}
}
/**
* Validate OTP token creating payload
* @param otpMailWrapper OTPMailWrapper
* @return true if its valid payload otherwise returns false
*/
private boolean isValidOTPTokenCreatingRequest(OTPMailWrapper otpMailWrapper) {
if (StringUtils.isBlank(otpMailWrapper.getFirstName())) {
log.error("Received empty or blank first name field with OTP creating payload.");
return false;
}
if (StringUtils.isBlank(otpMailWrapper.getLastName())) {
log.error("Received empty or blank last name field with OTP creating payload.");
return false;
}
if (StringUtils.isBlank(otpMailWrapper.getAdminUsername())) {
log.error("Received empty or blank admin username field with OTP creating payload.");
return false;
}
if (StringUtils.isBlank(otpMailWrapper.getAdminPassword())) {
log.error("Received empty or blank admin password field with OTP creating payload.");
return false;
}
if (StringUtils.isBlank(otpMailWrapper.getEmail())) {
log.error("Received empty or blank email field with OTP creating payload.");
return false;
}
if (StringUtils.isBlank(otpMailWrapper.getEmailType())) {
log.error("Received empty or blank email type field with OTP creating payload.");
return false;
}
if (StringUtils.isBlank(otpMailWrapper.getTenantDomain())) {
log.error("Received empty or blank tenant domain field with OTP creating payload.");
return false;
}
return true;
}
}

@ -0,0 +1,211 @@
/*
* Copyright (c) 2020, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. 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.device.mgt.core.otp.mgt.util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.exceptions.DBConnectionException;
import org.wso2.carbon.device.mgt.common.exceptions.TransactionManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.IllegalTransactionStateException;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* ConnectionManagerUtil is responsible for handling all the datasource connections utilities.
*/
public class ConnectionManagerUtil {
private static final Log log = LogFactory.getLog(ConnectionManagerUtil.class);
private static final ThreadLocal<Connection> currentConnection = new ThreadLocal<>();
private static DataSource dataSource;
public static void openDBConnection() throws DBConnectionException {
Connection conn = currentConnection.get();
if (conn != null) {
String msg = "Database connection has already been obtained.";
log.error(msg);
throw new IllegalTransactionStateException(msg);
}
try {
conn = dataSource.getConnection();
} catch (SQLException e) {
String msg = "Failed to get a database connection.";
log.error(msg, e);
throw new DBConnectionException(msg, e);
}
currentConnection.set(conn);
}
public static Connection getDBConnection() throws DBConnectionException {
Connection conn = currentConnection.get();
if (conn == null) {
try {
conn = dataSource.getConnection();
currentConnection.set(conn);
} catch (SQLException e) {
throw new DBConnectionException("Failed to get database connection.", e);
}
}
return conn;
}
public static void beginDBTransaction() throws TransactionManagementException, DBConnectionException {
Connection conn = currentConnection.get();
if (conn == null) {
conn = getDBConnection();
} else if (inTransaction(conn)) {
String msg = "Transaction has already been started.";
log.error(msg);
throw new IllegalTransactionStateException(msg);
}
try {
conn.setAutoCommit(false);
} catch (SQLException e) {
String msg = "Error occurred while starting a database transaction.";
log.error(msg, e);
throw new TransactionManagementException(msg, e);
}
}
public static void endDBTransaction() throws TransactionManagementException {
Connection conn = currentConnection.get();
if (conn == null) {
throw new IllegalTransactionStateException("Database connection is not active.");
}
if (!inTransaction(conn)) {
throw new IllegalTransactionStateException("Transaction has not been started.");
}
try {
conn.setAutoCommit(true);
} catch (SQLException e) {
throw new TransactionManagementException("Error occurred while ending database transaction.", e);
}
}
public static void commitDBTransaction() {
Connection conn = currentConnection.get();
if (conn == null) {
throw new IllegalTransactionStateException("Database connection is not active.");
}
if (!inTransaction(conn)) {
throw new IllegalTransactionStateException("Transaction has not been started.");
}
try {
conn.commit();
} catch (SQLException e) {
log.error("Error occurred while committing the transaction", e);
}
}
public static void rollbackDBTransaction() {
Connection conn = currentConnection.get();
if (conn == null) {
throw new IllegalTransactionStateException("Database connection is not active.");
}
if (!inTransaction(conn)) {
throw new IllegalTransactionStateException("Transaction has not been started.");
}
try {
conn.rollback();
} catch (SQLException e) {
log.warn("Error occurred while roll-backing the transaction", e);
}
}
public static void closeDBConnection() {
Connection conn = currentConnection.get();
if (conn == null) {
throw new IllegalTransactionStateException("Database connection is not active.");
}
try {
conn.close();
} catch (SQLException e) {
log.error("Error occurred while closing the connection", e);
}
currentConnection.remove();
}
private static boolean inTransaction(Connection conn) {
boolean inTransaction = true;
try {
if (conn.getAutoCommit()) {
inTransaction = false;
}
} catch (SQLException e) {
throw new IllegalTransactionStateException("Failed to get transaction state.");
}
return inTransaction;
}
public static boolean isTransactionStarted() throws DBConnectionException {
Connection connection = getDBConnection();
return inTransaction(connection);
}
/**
* Resolve the datasource from the datasource definition.
*
* @param dataSourceName Name of the datasource
* @return DataSource resolved by the datasource name
*/
public static DataSource resolveDataSource(String dataSourceName) {
try {
dataSource = InitialContext.doLookup(dataSourceName);
} catch (Exception e) {
throw new RuntimeException("Error in looking up data source: " + e.getMessage(), e);
}
return dataSource;
}
public static String getDatabaseType() {
try (Connection connection = dataSource.getConnection()) {
return connection.getMetaData().getDatabaseProductName();
} catch (SQLException e) {
log.error("Error occurred while retrieving config.datasource connection", e);
}
return null;
}
/**
* To check whether particular database that is used for application management supports batch query execution.
*
* @return true if batch query is supported, otherwise false.
*/
public static boolean isBatchQuerySupported() {
try (Connection connection = dataSource.getConnection()) {
return connection.getMetaData().supportsBatchUpdates();
} catch (SQLException e) {
log.error("Error occurred while checking whether database supports batch updates", e);
}
return false;
}
public static void init(DataSource dtSource) {
dataSource = dtSource;
}
}

@ -572,6 +572,23 @@ CREATE TABLE IF NOT EXISTS DM_METADATA (
);
-- END OF METADATA TABLE --
-- DM_OTP_DATA TABLE --
CREATE TABLE IF NOT EXISTS DM_OTP_DATA (
ID INT AUTO_INCREMENT NOT NULL,
OTP_TOKEN VARCHAR(100) NOT NULL,
TENANT_DOMAIN VARCHAR(20) NOT NULL,
EMAIL VARCHAR(100) NOT NULL,
EMAIL_TYPE VARCHAR(20) NOT NULL,
META_INFO VARCHAR(20000) NOT NULL,
CREATED_AT TIMESTAMP NOT NULL,
EXPIRY_TIME INT NOT NULL DEFAULT 3600,
IS_EXPIRED BOOLEAN DEFAULT false,
TENANT_CREATED BOOLEAN DEFAULT false,
PRIMARY KEY (ID),
CONSTRAINT email_type_uk UNIQUE (EMAIL, EMAIL_TYPE)
);
-- END OF DM_OTP_DATA TABLE --
-- DASHBOARD RELATED VIEWS --
CREATE VIEW POLICY_COMPLIANCE_INFO AS
SELECT

@ -614,6 +614,23 @@ CREATE TABLE DM_METADATA (
);
-- END OF METADATA TABLE --
-- DM_OTP_DATA TABLE --
CREATE TABLE DM_OTP_DATA (
ID INT IDENTITY NOT NULL,
OTP_TOKEN VARCHAR(100) NOT NULL,
TENANT_DOMAIN VARCHAR(20) NOT NULL,
EMAIL VARCHAR(100) NOT NULL,
EMAIL_TYPE VARCHAR(20) NOT NULL,
META_INFO VARCHAR(20000) NOT NULL,
CREATED_AT DATETIME2(0) NOT NULL,
EXPIRY_TIME INT NOT NULL DEFAULT 3600,
IS_EXPIRED BIT DEFAULT false,
TENANT_CREATED BOOLEAN DEFAULT false,
PRIMARY KEY (ID),
CONSTRAINT email_type_uk UNIQUE (EMAIL, EMAIL_TYPE)
);
-- END OF DM_OTP_DATA TABLE --
-- DASHBOARD RELATED VIEWS --
IF NOT EXISTS (SELECT * FROM SYS.VIEWS WHERE NAME = 'POLICY_COMPLIANCE_INFO')

@ -630,6 +630,23 @@ CREATE TABLE IF NOT EXISTS DM_METADATA (
) ENGINE=InnoDB;
-- END OF METADATA TABLE --
-- DM_OTP_DATA TABLE --
CREATE TABLE IF NOT EXISTS DM_OTP_DATA (
ID INT AUTO_INCREMENT NOT NULL,
OTP_TOKEN VARCHAR(100) NOT NULL,
TENANT_DOMAIN VARCHAR(20) NOT NULL,
EMAIL VARCHAR(100) NOT NULL,
EMAIL_TYPE VARCHAR(20) NOT NULL,
META_INFO VARCHAR(20000) NOT NULL,
CREATED_AT TIMESTAMP NOT NULL,
EXPIRY_TIME INT NOT NULL DEFAULT 3600,
IS_EXPIRED BOOLEAN DEFAULT false,
TENANT_CREATED BOOLEAN DEFAULT false,
PRIMARY KEY (ID),
CONSTRAINT email_type_uk UNIQUE (EMAIL, EMAIL_TYPE)
);
-- END OF DM_OTP_DATA TABLE --
-- DASHBOARD RELATED VIEWS --
CREATE VIEW DEVICE_INFO_VIEW AS

@ -978,6 +978,34 @@ END;
/
-- END OF METADATA TABLE --
-- OPT-DATA TABLE --
CREATE TABLE DM_OTP_DATA (
ID NUMBER(10) NOT NULL,
OTP_TOKEN VARCHAR2(100) NOT NULL,
TENANT_DOMAIN VARCHAR(20) NOT NULL,
EMAIL VARCHAR2(100) NOT NULL,
EMAIL_TYPE VARCHAR2(20) NOT NULL,
META_INFO VARCHAR2(20000) NOT NULL,
CREATED_AT TIMESTAMP(0) NOT NULL,
EXPIRY_TIME NUMBER(10) DEFAULT 3600 NOT NULL,
IS_EXPIRED CHAR(1) DEFAULT false,
TENANT_CREATED BOOLEAN DEFAULT false,
PRIMARY KEY (ID),
CONSTRAINT email_type_uk UNIQUE (EMAIL, EMAIL_TYPE)
);
-- Generate ID using sequence and trigger
CREATE SEQUENCE DM_OTP_DATA_seq START WITH 1 INCREMENT BY 1;
CREATE OR REPLACE TRIGGER DM_OTP_DATA_seq_tr
BEFORE INSERT ON DM_OTP_DATA FOR EACH ROW
WHEN (NEW.ID IS NULL)
BEGIN
SELECT DM_OTP_DATA_seq.NEXTVAL INTO :NEW.ID FROM DUAL;
END;
/
-- END OF OTP-DATA TABLE --
-- DASHBOARD RELATED VIEWS --
CREATE VIEW POLICY_COMPLIANCE_INFO AS

@ -617,6 +617,26 @@ CREATE TABLE IF NOT EXISTS DM_METADATA (
);
-- END OF METADATA TABLE --
-- OPT-DATA TABLE --
CREATE SEQUENCE DM_OTP_DATA_seq;
CREATE TABLE IF NOT EXISTS DM_OTP_DATA (
ID INT DEFAULT NEXTVAL ('DM_OTP_DATA_seq') NOT NULL,
OTP_TOKEN VARCHAR(100) NOT NULL,
TENANT_DOMAIN VARCHAR(20) NOT NULL,
EMAIL VARCHAR(100) NOT NULL,
EMAIL_TYPE VARCHAR(20) NOT NULL,
META_INFO VARCHAR(20000) NOT NULL,
CREATED_AT TIMESTAMP(0) NOT NULL,
EXPIRY_TIME INT NOT NULL DEFAULT 3600,
IS_EXPIRED BOOLEAN DEFAULT false,
TENANT_CREATED BOOLEAN DEFAULT false,
PRIMARY KEY (ID),
CONSTRAINT email_type_uk UNIQUE (EMAIL, EMAIL_TYPE)
);
-- END OF OPT-DATA TABLE --
-- DASHBOARD RELATED VIEWS --
CREATE VIEW DEVICE_INFO_VIEW AS

@ -0,0 +1,230 @@
#*
Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
Entgra (Pvt) Ltd. 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.
*#
<EmailConfig>
<Subject>You have been invited to enroll your device in Entgra IoT</Subject>
<Body>
<![CDATA[
<html>
<head>
<title>Entgra IoT Server</title>
</head>
<body style="color: #666666; background-color:#cdcdcd; padding: 0px; margin: 0px;">
<div style="background-color:#cdcdcd; font-length: 1em; font-family: Arial, Helvetica; line-height: 170%; color: #666666; padding: 20px 0px; margin: 0px;">
<div style="width: 86%; max-width: 650px; padding: 2%; background-color: #ffffff; margin: auto; border-radius: 14px;">
<div style="background-color: #ffebcc; line-height: 0px; border-top-left-radius: 10px; border-top-right-radius: 10px; padding: 10px;">
<div style="display: inline-block; line-height: 0px;">
<img src="data:image/png;charset=utf-8;base64,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
TkSuQmCC"
alt="entgra.io"/>
</div>
</div>
<div style="background-color: #ffffff; line-height: 170%; color: #666666; padding: 20px 25px;">
<p style="font-length: 1em; font-family: Arial, Helvetica; line-height: 170%; color: #666666; margin: 5px 0px 20px;">
Hi $first-name,
</p>
<p style="font-size: 1em; font-family: Arial, Helvetica; line-height: 170%; color: #666666; margin: 5px 0px;">
Congratulations!!! Thank you for registering with Entgra cloud. Please click and log in to the
following link to complete your registration with us. Click <a href="$base-url-https/self-register?token=$otp-token">here</a>.
</p>
<p style="font-length: 1em; font-family: Arial, Helvetica; line-height: 170%; color: #666666; margin: 5px 0px;">
If you need further assistance, please contact your administrator.
</p>
<p style="font-length: 1em; font-family: Arial, Helvetica; line-height: 170%; color: #666666; margin: 20px 0px 5px;">
Regards,
</p>
<p style="font-size: 1em; font-family: Arial, Helvetica; line-height: 170%; color: #666666; margin: 5px 0px;">
Entgra IoT Administrator
</p>
</div>
</div>
</div>
</body>
</html>
]]>
</Body>
</EmailConfig>
Loading…
Cancel
Save