diff --git a/components/device-mgt/io.entgra.carbon.device.mgt.config.api/src/main/java/io/entgra/carbon/device/mgt/config/jaxrs/service/DeviceManagementConfigService.java b/components/device-mgt/io.entgra.carbon.device.mgt.config.api/src/main/java/io/entgra/carbon/device/mgt/config/jaxrs/service/DeviceManagementConfigService.java index 4742548f66d..14e0e1ded1f 100644 --- a/components/device-mgt/io.entgra.carbon.device.mgt.config.api/src/main/java/io/entgra/carbon/device/mgt/config/jaxrs/service/DeviceManagementConfigService.java +++ b/components/device-mgt/io.entgra.carbon.device.mgt.config.api/src/main/java/io/entgra/carbon/device/mgt/config/jaxrs/service/DeviceManagementConfigService.java @@ -18,6 +18,7 @@ package io.entgra.carbon.device.mgt.config.jaxrs.service; +import org.wso2.carbon.device.mgt.common.DeviceTransferRequest; import io.entgra.carbon.device.mgt.config.jaxrs.beans.ErrorResponse; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -33,12 +34,11 @@ import io.swagger.annotations.Tag; import org.wso2.carbon.apimgt.annotations.api.Scope; import org.wso2.carbon.apimgt.annotations.api.Scopes; import org.wso2.carbon.device.mgt.common.configuration.mgt.DeviceConfiguration; -import org.wso2.carbon.device.mgt.common.search.PropertyMap; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; -import javax.ws.rs.POST; +import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; @@ -63,7 +63,6 @@ import javax.ws.rs.core.Response; ) @Path("/configurations") @Api(value = "Device Management Configuration") -@Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Scopes(scopes = { @Scope( @@ -71,6 +70,12 @@ import javax.ws.rs.core.Response; description = "", key = "perm:view-configuration", permissions = {"/device-mgt/platform-configurations/view"} + ), + @Scope( + name = "Manage configurations", + description = "", + key = "perm:manage-configuration", + permissions = {"/device-mgt/platform-configurations/manage"} ) } ) @@ -119,16 +124,74 @@ public interface DeviceManagementConfigService { "fetching device configurations.", response = ErrorResponse.class) }) - Response getConfiguration(@ApiParam( - name = "token", - value = "value for identify an already enrolled and authorized device", - required = true) - @HeaderParam("token") - String token, - @ApiParam( - name = "properties", - value = "The properties list using for query a device", - required = true) - @QueryParam("properties") - String properties); + @Produces(MediaType.APPLICATION_JSON) + Response getConfiguration( + @ApiParam( + name = "token", + value = "value for identify an already enrolled and authorized device", + required = true) + @HeaderParam("token") + String token, + @ApiParam( + name = "properties", + value = "The properties list using for query a device", + required = true) + @QueryParam("properties") + String properties); + + @PUT + @Path("/transfer") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "PUT", + value = "Transfer device to another tenant from super tenant", + notes = "This API is responsible for transfer device from super tenant to another tenant", + tags = "Device Management Configuration", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = "scope", value = "perm:manage-configuration") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully transferred the device.", + response = DeviceConfiguration.class, + responseContainer = "List", + 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 has been modified " + + "the last time.Used by caches, or in " + + "conditional requests."), + } + ), + @ApiResponse( + code = 400, + message = "Bad request.\n The request contains invalid parameters"), + @ApiResponse( + code = 401, + message = "Unauthorized.\n The requested is not authorized"), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n Server error occurred while " + + "fetching device configurations.", + response = ErrorResponse.class) + }) + @Produces(MediaType.APPLICATION_JSON) + Response transferDevices( + @ApiParam( + name = "Device Transfer Request", + value = "The device transfer request", + required = true) + DeviceTransferRequest deviceTransferRequest); } diff --git a/components/device-mgt/io.entgra.carbon.device.mgt.config.api/src/main/java/io/entgra/carbon/device/mgt/config/jaxrs/service/impl/DeviceManagementConfigServiceImpl.java b/components/device-mgt/io.entgra.carbon.device.mgt.config.api/src/main/java/io/entgra/carbon/device/mgt/config/jaxrs/service/impl/DeviceManagementConfigServiceImpl.java index b34f964b177..93aba13627a 100644 --- a/components/device-mgt/io.entgra.carbon.device.mgt.config.api/src/main/java/io/entgra/carbon/device/mgt/config/jaxrs/service/impl/DeviceManagementConfigServiceImpl.java +++ b/components/device-mgt/io.entgra.carbon.device.mgt.config.api/src/main/java/io/entgra/carbon/device/mgt/config/jaxrs/service/impl/DeviceManagementConfigServiceImpl.java @@ -26,8 +26,10 @@ import io.entgra.carbon.device.mgt.config.jaxrs.service.DeviceManagementConfigSe import io.entgra.carbon.device.mgt.config.jaxrs.util.DeviceMgtAPIUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.device.mgt.common.AppRegistrationCredentials; import org.wso2.carbon.device.mgt.common.ApplicationRegistrationException; +import org.wso2.carbon.device.mgt.common.DeviceTransferRequest; import org.wso2.carbon.device.mgt.common.configuration.mgt.AmbiguousConfigurationException; import org.wso2.carbon.device.mgt.common.configuration.mgt.DeviceConfiguration; import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException; @@ -41,16 +43,17 @@ import org.wso2.carbon.identity.jwt.client.extension.exception.JWTClientExceptio import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; +import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.io.IOException; +import java.util.List; import java.util.Map; @Path("/configurations") -@Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class DeviceManagementConfigServiceImpl implements DeviceManagementConfigService { @@ -58,6 +61,7 @@ public class DeviceManagementConfigServiceImpl implements DeviceManagementConfig @Override @GET + @Produces(MediaType.APPLICATION_JSON) public Response getConfiguration(@HeaderParam("token") String token, @QueryParam("properties") String properties) { DeviceManagementProviderService dms = DeviceMgtAPIUtils.getDeviceManagementService(); @@ -115,6 +119,41 @@ public class DeviceManagementConfigServiceImpl implements DeviceManagementConfig } } + @PUT + @Path("/transfer") + @Override + @Produces(MediaType.APPLICATION_JSON) + public Response transferDevices(DeviceTransferRequest deviceTransferRequest) { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + int tenantId = ctx.getTenantId(false); + if (tenantId != -1234) { + return Response.status(Response.Status.FORBIDDEN).entity("Tenant '" + ctx.getTenantDomain(true) + + "' does not have privilege to transfer device").build(); + } + DeviceManagementProviderService dms = DeviceMgtAPIUtils.getDeviceManagementService(); + try { + List devicesTransferred = dms.transferDeviceToTenant(deviceTransferRequest); + if (devicesTransferred.isEmpty()) { + String msg = "Devices are not enrolled to super tenant"; + log.warn(msg); + return Response.status(Response.Status.BAD_REQUEST).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); + } else { + return Response.status(Response.Status.OK).entity(devicesTransferred).build(); + } + } catch (DeviceManagementException e) { + String msg = "Error occurred while transferring device to tenant " + + deviceTransferRequest.getDestinationTenant(); + log.error(msg, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); + } catch (DeviceNotFoundException e) { + log.error(e.getMessage(), e); + return Response.status(Response.Status.BAD_REQUEST).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(e.getMessage()).build()).build(); + } + } + private String parseUriParamsToJSON(String uriParams) { uriParams = uriParams.replaceAll("=", "\":\""); uriParams = uriParams.replaceAll("&", "\",\""); diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/DeviceTransferRequest.java b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/DeviceTransferRequest.java new file mode 100644 index 00000000000..2a73c4dccf5 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/DeviceTransferRequest.java @@ -0,0 +1,48 @@ +package org.wso2.carbon.device.mgt.common; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.util.List; + +/** + * Represents an individual configuration entry. + */ +@ApiModel(value = "DeviceTransferRequest", description = "This class carries all information related to device " + + "transfer from super tenant to another tenant.") +public class DeviceTransferRequest { + + @ApiModelProperty(name = "deviceType", value = "Type of the device", required = true) + private String deviceType; + + @ApiModelProperty(name = "deviceIds", value = "Ids of devices to transfer", required = true) + private List deviceIds; + + @ApiModelProperty(name = "destinationTenant", value = "Destination Tenant ID", required = true) + private String destinationTenant; + + public String getDeviceType() { + return deviceType; + } + + public void setDeviceType(String deviceType) { + this.deviceType = deviceType; + } + + public List getDeviceIds() { + return deviceIds; + } + + public void setDeviceId(List deviceIds) { + this.deviceIds = deviceIds; + } + + public String getDestinationTenant() { + return destinationTenant; + } + + public void setDestinationTenant(String destinationTenant) { + this.destinationTenant = destinationTenant; + } + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/pom.xml b/components/device-mgt/org.wso2.carbon.device.mgt.core/pom.xml index 5a4eafe577d..560b693618d 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/pom.xml +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/pom.xml @@ -356,6 +356,10 @@ org.powermock powermock-api-mockito + + org.wso2.carbon.multitenancy + org.wso2.carbon.tenant.mgt + diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java index f824a2255b9..c8c991b71dd 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java @@ -45,6 +45,7 @@ import org.wso2.carbon.device.mgt.core.dto.DeviceType; import org.wso2.carbon.device.mgt.core.geo.GeoCluster; import org.wso2.carbon.device.mgt.core.geo.geoHash.GeoCoordinate; +import java.sql.SQLException; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -529,4 +530,7 @@ public interface DeviceDAO { * @throws DeviceManagementDAOException */ void deleteDevice(DeviceIdentifier deviceIdentifier, int tenantId) throws DeviceManagementDAOException; + + boolean transferDevice(String deviceType, String deviceId, String owner, int destinationTenantId) + throws DeviceManagementDAOException, SQLException; } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractDeviceDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractDeviceDAOImpl.java index 6e5ef3a5861..5691b562dd0 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractDeviceDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractDeviceDAOImpl.java @@ -1937,4 +1937,49 @@ public abstract class AbstractDeviceDAOImpl implements DeviceDAO { DeviceManagementDAOUtil.cleanupResources(stmt, null); } } + + public boolean transferDevice(String deviceType, String deviceIdentifier, String owner, int destinationTenantId) + throws DeviceManagementDAOException, SQLException { + Connection conn = this.getConnection(); + int deviceId = getDeviceId(conn, new DeviceIdentifier(deviceIdentifier, deviceType), -1234); + PreparedStatement stmt = null; + try { + String sql = "UPDATE DM_DEVICE SET TENANT_ID = ? WHERE ID = ? AND TENANT_ID = -1234"; + stmt = conn.prepareStatement(sql); + stmt.setInt(1, destinationTenantId); + stmt.setInt(2, deviceId); + stmt.executeUpdate(); + } catch (SQLException e) { + conn.rollback(); + throw new DeviceManagementDAOException("Error occurred while removing device", e); + } finally { + DeviceManagementDAOUtil.cleanupResources(stmt, null); + } + try { + String sql = "UPDATE DM_DEVICE_PROPERTIES SET TENANT_ID = ? " + + "WHERE DEVICE_TYPE_NAME = ? AND DEVICE_IDENTIFICATION = ? AND TENANT_ID = -1234"; + stmt = conn.prepareStatement(sql); + stmt.setInt(1, destinationTenantId); + stmt.setString(2, deviceType); + stmt.setString(3, deviceIdentifier); + stmt.executeUpdate(); + } catch (SQLException e) { + throw new DeviceManagementDAOException("Error occurred while removing device", e); + } finally { + DeviceManagementDAOUtil.cleanupResources(stmt, null); + } + try { + String sql = "UPDATE DM_ENROLMENT SET TENANT_ID = ?, OWNER = ? WHERE DEVICE_ID = ? AND TENANT_ID = -1234"; + stmt = conn.prepareStatement(sql); + stmt.setInt(1, destinationTenantId); + stmt.setString(2, owner); + stmt.setInt(3, deviceId); + stmt.executeUpdate(); + } catch (SQLException e) { + throw new DeviceManagementDAOException("Error occurred while removing device", e); + } finally { + DeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return true; + } } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderService.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderService.java index 10cbe96df23..812363d1eff 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderService.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderService.java @@ -37,6 +37,7 @@ package org.wso2.carbon.device.mgt.core.service; import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceTransferRequest; import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException; import org.wso2.carbon.device.mgt.common.exceptions.DeviceNotFoundException; import org.wso2.carbon.device.mgt.common.exceptions.DeviceTypeNotFoundException; @@ -760,4 +761,12 @@ public interface DeviceManagementProviderService { DeviceConfiguration getDeviceConfiguration(Map propertyMap) throws DeviceManagementException, DeviceNotFoundException, UnauthorizedDeviceAccessException, AmbiguousConfigurationException; + + /** + * Transfer device from super tenant to another tenant + * + * @param deviceTransferRequest DTO of the transfer request + * @return tru if device transferee, otherwise false + */ + List transferDeviceToTenant(DeviceTransferRequest deviceTransferRequest) throws DeviceManagementException, DeviceNotFoundException; } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderServiceImpl.java index c6fa1ebb71f..dd552c6868e 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderServiceImpl.java @@ -51,6 +51,7 @@ import org.wso2.carbon.device.mgt.analytics.data.publisher.exception.DataPublish import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.common.DeviceEnrollmentInfoNotification; import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceTransferRequest; import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException; import org.wso2.carbon.device.mgt.common.DeviceManager; import org.wso2.carbon.device.mgt.common.exceptions.DeviceNotFoundException; @@ -129,6 +130,8 @@ import org.wso2.carbon.email.sender.core.EmailSendingFailedException; import org.wso2.carbon.email.sender.core.EmailTransportNotConfiguredException; import org.wso2.carbon.email.sender.core.TypedValue; import org.wso2.carbon.email.sender.core.service.EmailSenderService; +import org.wso2.carbon.stratos.common.beans.TenantInfoBean; +import org.wso2.carbon.tenant.mgt.services.TenantMgtAdminService; import org.wso2.carbon.user.api.UserStoreException; import javax.xml.bind.JAXBContext; @@ -3503,6 +3506,65 @@ public class DeviceManagementProviderServiceImpl implements DeviceManagementProv } } + @Override + public List transferDeviceToTenant(DeviceTransferRequest deviceTransferRequest) + throws DeviceManagementException, DeviceNotFoundException { + if (log.isDebugEnabled()) { + log.debug("Attempting to transfer devices to '" + + deviceTransferRequest.getDestinationTenant() + "'"); + } + List enrolledDevices = new ArrayList<>(); + DeviceIdentifier deviceIdentifier; + for (String deviceId : deviceTransferRequest.getDeviceIds()) { + deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(deviceTransferRequest.getDeviceType()); + if (isEnrolled(deviceIdentifier)) { + enrolledDevices.add(deviceId); + } else { + log.warn("Device '" + deviceId + "' is not enrolled with super tenant. Hence excluding from transferring"); + } + } + + if (enrolledDevices.isEmpty()) { + throw new DeviceNotFoundException("No any enrolled device found to transfer"); + } + + int destinationTenantId; + String owner; + try { + TenantMgtAdminService tenantMgtAdminService = new TenantMgtAdminService(); + TenantInfoBean tenantInfoBean = tenantMgtAdminService.getTenant(deviceTransferRequest.getDestinationTenant()); + destinationTenantId = tenantInfoBean.getTenantId(); + owner = tenantInfoBean.getAdmin(); + } catch (Exception e) { + String msg = "Error getting destination tenant id and admin from domain'" + + deviceTransferRequest.getDestinationTenant() + "'"; + log.error(msg); + throw new DeviceManagementException(msg, e); + } + try { + DeviceManagementDAOFactory.openConnection(); + List movedDevices = new ArrayList<>(); + for (String deviceId : enrolledDevices) { + if (deviceDAO.transferDevice(deviceTransferRequest.getDeviceType(), deviceId, owner, destinationTenantId)){ + movedDevices.add(deviceId); + } else { + log.warn("Device '" + deviceId + "' not transferred to tenant " + destinationTenantId); + } + } + DeviceManagementDAOFactory.commitTransaction(); + return movedDevices; + } catch (SQLException | DeviceManagementDAOException e) { + DeviceManagementDAOFactory.rollbackTransaction(); + String msg = "Error in transferring devices to tenant '" + deviceTransferRequest.getDestinationTenant() + "'"; + log.error(msg); + throw new DeviceManagementException(msg, e); + } finally { + DeviceManagementDAOFactory.closeConnection(); + } + } + /** * Wrap the device configuration data into DeviceConfiguration bean * @param device Device queried using the properties diff --git a/pom.xml b/pom.xml index dec0bcd9e8c..1a6f5035224 100644 --- a/pom.xml +++ b/pom.xml @@ -1761,6 +1761,12 @@ ${tomcat.websocket.version} provided + + org.wso2.carbon.multitenancy + org.wso2.carbon.tenant.mgt + ${carbon.multitenancy.version} + provided +