diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/admin/DeviceTypePublisherAdminService.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/admin/DeviceTypePublisherAdminService.java new file mode 100644 index 0000000000..7914f9d071 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/admin/DeviceTypePublisherAdminService.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2017, 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.jaxrs.service.api.admin; + + +import io.swagger.annotations.*; +import org.wso2.carbon.apimgt.annotations.api.Scope; +import org.wso2.carbon.apimgt.annotations.api.Scopes; +import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse; +import org.wso2.carbon.device.mgt.jaxrs.util.Constants; + +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +@SwaggerDefinition( + info = @Info( + version = "1.0.0", + title = "", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = "name", value = "DeviceTypePublisherAdminService"), + @ExtensionProperty(name = "context", value = "/api/device-mgt/v1.0/admin/devicetype"), + }) + } + ), + tags = { + @Tag(name = "device_management", description = "") + } +) +@Path("/admin/devicetype") +@Api(value = "Devicetype deployment Administrative Service", description = "This an API intended to be used to " + + "deploy device type components" + + "Further, this is strictly restricted to admin users only ") +@Scopes( + scopes = { + @Scope( + name = "Devicetype deployment", + description = "Deploy devicetype", + key = "perm:devicetype:deployment", + permissions = {"/device-mgt/devicetype/deploy"} + ) + } +) + +public interface DeviceTypePublisherAdminService { + + @POST + @Path("/deploy/{type}") + @ApiOperation( + httpMethod = "POST", + value = "Deploy device type\n", + notes = "This is an API that can be used to deploy existing device type artifact for tenant", + response = Response.class, + tags = "Devicetype Deployment Service", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devicetype:deployment") + }) + }) + + @ApiResponses(value = { + @ApiResponse( + code = 201, + message = "OK. \n Successfully deployed the artifacts.", + response = Response.class), + @ApiResponse( + code = 400, + message = "Bad Request. \n Invalid request or validation error.", + response = ErrorResponse.class), + @ApiResponse( + code = 404, + message = "Not Found. \n The specified resource does not exist."), + @ApiResponse( + code = 415, + message = "Unsupported media type. \n The entity of the request was in a not supported format."), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n Server error occurred while checking the authorization" + + " for a specified set of devices.", + response = ErrorResponse.class) + }) + + Response doPublish( + @ApiParam(name = "type", + value = "The type of deployment." + + "INFO: Deploy artifact with given type.", + required = true) + @PathParam("type") String type); + + @GET + @Path("/deploy/{type}/status") + @ApiOperation( + httpMethod = "GET", + value = "Check the status of device type artifact\n", + notes = "This is an API that can be used to check the status of the artifact", + response = Response.class, + tags = "Devicetype Status Service", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devicetype:deployment") + }) + }) + + @ApiResponses(value = { + @ApiResponse( + code = 201, + message = "OK. \n Successfully deployed the artifacts.", + response = Response.class), + @ApiResponse( + code = 400, + message = "Bad Request. \n Invalid request or validation error.", + response = ErrorResponse.class), + @ApiResponse( + code = 404, + message = "Not Found. \n The specified resource does not exist."), + @ApiResponse( + code = 415, + message = "Unsupported media type. \n The entity of the request was in a not supported format."), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n Server error occurred while checking the authorization" + + " for a specified set of devices.", + response = ErrorResponse.class) + }) + + Response getStatus(@PathParam("type") String deviceType); + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/admin/DeviceTypePublisherAdminServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/admin/DeviceTypePublisherAdminServiceImpl.java new file mode 100644 index 0000000000..66548fee52 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/admin/DeviceTypePublisherAdminServiceImpl.java @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2017, 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.jaxrs.service.impl.admin; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMXMLBuilderFactory; +import org.apache.axis2.client.Options; +import org.apache.axis2.java.security.SSLProtocolSocketFactory; +import org.apache.axis2.transport.http.HTTPConstants; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.application.mgt.stub.upload.CarbonAppUploaderStub; +import org.wso2.carbon.application.mgt.stub.upload.types.carbon.UploadedFileItem; +import org.wso2.carbon.base.ServerConfiguration; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.core.util.Utils; +import org.wso2.carbon.device.mgt.jaxrs.service.api.admin.DeviceTypePublisherAdminService; +import org.wso2.carbon.device.mgt.jaxrs.util.DeviceMgtAPIUtils; +import org.wso2.carbon.registry.core.Registry; +import org.wso2.carbon.registry.core.Resource; +import org.wso2.carbon.registry.core.ResourceImpl; +import org.wso2.carbon.registry.core.exceptions.RegistryException; +import org.wso2.carbon.utils.CarbonUtils; + +import javax.activation.DataHandler; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Response; +import java.io.*; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.List; + +@Path("/admin/devicetype") +public class DeviceTypePublisherAdminServiceImpl implements DeviceTypePublisherAdminService { + + /** + * required soap header for mutualSSL + */ + private static final String USER_NAME_HEADER = "UserName"; + + private static final String KEY_STORE_TYPE = "JKS"; + /** + * Default truststore type of the client + */ + private static final String TRUST_STORE_TYPE = "JKS"; + /** + * Default keymanager type of the client + */ + private static final String KEY_MANAGER_TYPE = "SunX509"; //Default Key Manager Type + /** + * Default trustmanager type of the client + */ + private static final String TRUST_MANAGER_TYPE = "SunX509"; //Default Trust Manager Type + + private static final String SSLV3 = "SSLv3"; + + private KeyStore keyStore; + private KeyStore trustStore; + private char[] keyStorePassword; + private SSLContext sslContext; + + private static final Log log = LogFactory.getLog(DeviceTypePublisherAdminServiceImpl.class); + private static final String DEFAULT_RESOURCE_LOCATION = "/resources/devicetypes"; + private static final String CAR_FILE_LOCATION = CarbonUtils.getCarbonHome() + File.separator + "repository" + + File.separator + "resources" + File.separator + "devicetypes"; + private static final String DAS_PORT = "${iot.analytics.https.port}"; + private static final String DAS_HOST_NAME = "${iot.analytics.host}"; + private static final String DEFAULT_HTTP_PROTOCOL = "https"; + private static final String IOT_MGT_PORT = "${iot.manager.https.port}"; + private static final String IOT_MGT_HOST_NAME = "${iot.manager.host}"; + private static final String DAS_URL = DEFAULT_HTTP_PROTOCOL + "://" + DAS_HOST_NAME + + ":" + DAS_PORT + "/services/CarbonAppUploader" + "/"; + private static final String IOT_MGT_URL = DEFAULT_HTTP_PROTOCOL + "://" + IOT_MGT_HOST_NAME + + ":" + IOT_MGT_PORT + "/services/CarbonAppUploader" + "/"; + private static final String MEDIA_TYPE_XML = "application/xml"; + private static final String DEVICE_MANAGEMENT_TYPE = "device_management"; + + @Override + @POST + @Path("/deploy/{type}") + public Response doPublish(@PathParam("type") String type) { + + try { + //Getting the tenant Domain + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + String username = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(); + String tenantAdminUser = username + "@" + tenantDomain; + + String keyStorePassword = ServerConfiguration.getInstance().getFirstProperty("Security.KeyStore.Password"); + String trustStorePassword = ServerConfiguration.getInstance().getFirstProperty( + "Security.TrustStore.Password"); + String keyStoreLocation = ServerConfiguration.getInstance().getFirstProperty("Security.KeyStore.Location"); + String trustStoreLocation = ServerConfiguration.getInstance().getFirstProperty( + "Security.TrustStore.Location"); + + //Call to load the keystore. + loadKeyStore(keyStoreLocation, keyStorePassword); + //Call to load the TrustStore. + loadTrustStore(trustStoreLocation, trustStorePassword); + //Create the SSL context with the loaded TrustStore/keystore. + initMutualSSLConnection(); + + //Constructing the soap header that required for mutual SSL + String strHeader = + "'" + tenantAdminUser + + "'"; + + InputStream is = new ByteArrayInputStream(strHeader.getBytes()); + OMElement header = OMXMLBuilderFactory.createOMBuilder(is).getDocumentElement(); + + List
list = new ArrayList
(); + Header httpHeader = new Header(); + httpHeader.setName(USER_NAME_HEADER); + byte[] encodedBytes = Base64.encodeBase64(tenantAdminUser.getBytes()); + httpHeader.setValue(new String(encodedBytes)); + list.add(httpHeader);//"https" + + File directory = new File(CAR_FILE_LOCATION + File.separator + type); + if (directory.isDirectory() && directory.exists()) { + UploadedFileItem[] uploadedFileItems = loadCappFromFileSystem(type); + if (uploadedFileItems.length > 0) { + CarbonAppUploaderStub carbonAppUploaderStub = new CarbonAppUploaderStub(Utils.replaceSystemProperty( + IOT_MGT_URL)); + carbonAppUploaderStub._getServiceClient().addHeader(header); + Options appUploaderOptions = carbonAppUploaderStub._getServiceClient().getOptions(); + if (appUploaderOptions == null) { + appUploaderOptions = new Options(); + } + appUploaderOptions.setProperty(HTTPConstants.HTTP_HEADERS, list); + appUploaderOptions.setProperty(HTTPConstants.CUSTOM_PROTOCOL_HANDLER + , new Protocol(DEFAULT_HTTP_PROTOCOL, (ProtocolSocketFactory) new SSLProtocolSocketFactory + (sslContext), Integer.parseInt(Utils.replaceSystemProperty(IOT_MGT_PORT)))); + + carbonAppUploaderStub._getServiceClient().setOptions(appUploaderOptions); + carbonAppUploaderStub.uploadApp(uploadedFileItems); + + if (!DEVICE_MANAGEMENT_TYPE.equals(type.toLowerCase())) { + carbonAppUploaderStub = new CarbonAppUploaderStub(Utils.replaceSystemProperty(DAS_URL)); + carbonAppUploaderStub._getServiceClient().addHeader(header); + appUploaderOptions = carbonAppUploaderStub._getServiceClient().getOptions(); + if (appUploaderOptions == null) { + appUploaderOptions = new Options(); + } + appUploaderOptions.setProperty(HTTPConstants.HTTP_HEADERS, list); + appUploaderOptions.setProperty(HTTPConstants.CUSTOM_PROTOCOL_HANDLER + , new Protocol(DEFAULT_HTTP_PROTOCOL + , (ProtocolSocketFactory) new SSLProtocolSocketFactory(sslContext) + , Integer.parseInt(Utils.replaceSystemProperty(DAS_PORT)))); + + carbonAppUploaderStub._getServiceClient().setOptions(appUploaderOptions); + carbonAppUploaderStub.uploadApp(uploadedFileItems); + } + int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); + Registry registry = DeviceMgtAPIUtils.getRegistryService().getConfigSystemRegistry(tenantId); + if (!registry.resourceExists(DEFAULT_RESOURCE_LOCATION + type + ".exist")) { + Resource resource = new ResourceImpl(); + resource.setContent(""); + resource.setMediaType(MEDIA_TYPE_XML); + registry.put(DEFAULT_RESOURCE_LOCATION + type + ".exist", resource); + } + } + } else { + return Response.status(Response.Status.BAD_REQUEST) + .entity("\"Error, Artifact does not exist.\"").build(); + } + + } catch (Exception e) { + log.error("Capp deployment failed due to " + e.getMessage(), e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + "\"Error, Artifact deployment has failed\"").build(); + } + + return Response.status(Response.Status.CREATED).entity("\"OK. \\n Successfully uploaded the artifacts.\"") + .build(); + } + + @GET + @Path("/deploy/{type}/status") + @Override + public Response getStatus(@PathParam("type") String deviceType) { + int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); + Registry registry = null; + try { + registry = DeviceMgtAPIUtils.getRegistryService().getConfigSystemRegistry(tenantId); + if (registry.resourceExists(DEFAULT_RESOURCE_LOCATION + deviceType + ".exist")) { + return Response.status(Response.Status.OK).entity("Exist").build(); + } else { + return Response.status(Response.Status.NO_CONTENT).entity("Does not Exist").build(); + } + } catch (RegistryException e) { + log.error("Registry failed to load." + e.getMessage(), e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + "\"Error, Artifact status check has failed\"").build(); + } + } + + private UploadedFileItem[] loadCappFromFileSystem(String deviceType) throws IOException { + + File directory = new File(CAR_FILE_LOCATION + File.separator + deviceType); + File[] carFiles = directory.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.toLowerCase().endsWith(".car"); + } + }); + List uploadedFileItemLis = new ArrayList<>(); + if (carFiles != null) { + + for (File carFile : carFiles) { + UploadedFileItem uploadedFileItem = new UploadedFileItem(); + DataHandler param = new DataHandler(carFile.toURI().toURL()); + uploadedFileItem.setDataHandler(param); + uploadedFileItem.setFileName(carFile.getName()); + uploadedFileItem.setFileType("jar"); + uploadedFileItemLis.add(uploadedFileItem); + } + } + UploadedFileItem[] fileItems = new UploadedFileItem[uploadedFileItemLis.size()]; + fileItems = uploadedFileItemLis.toArray(fileItems); + return fileItems; + } + + /** + * Loads the keystore. + * + * @param keyStorePath - the path of the keystore + * @param ksPassword - the keystore password + */ + private void loadKeyStore(String keyStorePath, String ksPassword) + throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { + InputStream fis = null; + try { + keyStorePassword = ksPassword.toCharArray(); + keyStore = KeyStore.getInstance(KEY_STORE_TYPE); + fis = new FileInputStream(keyStorePath); + keyStore.load(fis, keyStorePassword); + } finally { + if (fis != null) { + fis.close(); + } + } + } + + /** + * Loads the trustore + * + * @param trustStorePath - the trustore path in the filesystem. + * @param tsPassword - the truststore password + */ + private void loadTrustStore(String trustStorePath, String tsPassword) + throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { + + InputStream fis = null; + try { + trustStore = KeyStore.getInstance(TRUST_STORE_TYPE); + fis = new FileInputStream(trustStorePath); + trustStore.load(fis, tsPassword.toCharArray()); + } finally { + if (fis != null) { + fis.close(); + } + } + } + + /** + * Initializes the SSL Context + */ + private void initMutualSSLConnection() throws NoSuchAlgorithmException, UnrecoverableKeyException, + KeyStoreException, KeyManagementException { + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KEY_MANAGER_TYPE); + keyManagerFactory.init(keyStore, keyStorePassword); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TRUST_MANAGER_TYPE); + trustManagerFactory.init(trustStore); + + // Create and initialize SSLContext for HTTPS communication + sslContext = SSLContext.getInstance(SSLV3); + sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + SSLContext.setDefault(sslContext); + } + + +} +