Merge branch 'windows-app-publisher' into 'corrective-policy'

Add Windows enterprise app installation support for publisher and store

See merge request entgra/carbon-device-mgt!642
corrective-policy
Dharmakeerthi Lasantha 4 years ago
commit f175fefe07

@ -17,5 +17,5 @@
package org.wso2.carbon.device.application.mgt.common; package org.wso2.carbon.device.application.mgt.common;
public enum DeviceTypes { public enum DeviceTypes {
ANDROID, IOS ANDROID, IOS, WINDOWS
} }

@ -31,6 +31,10 @@ public class Application {
required = true) required = true)
private String name; private String name;
@ApiModelProperty(name = "installerName",
value = "Application Installer Name")
private String installerName;
@ApiModelProperty(name = "description", @ApiModelProperty(name = "description",
value = "Description of the application", value = "Description of the application",
required = true) required = true)
@ -173,4 +177,8 @@ public class Application {
public boolean isAndroidEnterpriseApp() { return isAndroidEnterpriseApp; } public boolean isAndroidEnterpriseApp() { return isAndroidEnterpriseApp; }
public void setAndroidEnterpriseApp(boolean androidEnterpriseApp) { isAndroidEnterpriseApp = androidEnterpriseApp; } public void setAndroidEnterpriseApp(boolean androidEnterpriseApp) { isAndroidEnterpriseApp = androidEnterpriseApp; }
public String getInstallerName() { return installerName; }
public void setInstallerName(String installerName) { this.installerName = installerName; }
} }

@ -87,6 +87,10 @@ public class ApplicationRelease {
value = "Application Rating") value = "Application Rating")
private double rating; private double rating;
@ApiModelProperty(name = "packageName",
value = "package name of the application")
private String packageName;
public String getReleaseType() { public String getReleaseType() {
return releaseType; return releaseType;
} }
@ -162,4 +166,8 @@ public class ApplicationRelease {
public List<String> getScreenshots() { return screenshots; } public List<String> getScreenshots() { return screenshots; }
public void setScreenshots(List<String> screenshots) { this.screenshots = screenshots; } public void setScreenshots(List<String> screenshots) { this.screenshots = screenshots; }
public String getPackageName() { return packageName; }
public void setPackageName(String packageName) { this.packageName = packageName; }
} }

@ -281,6 +281,16 @@ public interface ApplicationManager {
String getInstallableLifecycleState() throws ApplicationManagementException; String getInstallableLifecycleState() throws ApplicationManagementException;
/**
* Check if there are subscription devices for operations
*
* @param operationId Id of the operation
* @param deviceId deviceId of the relevant device
* @return boolean value either true or false according to the situation
* @throws ApplicationManagementException
*/
boolean checkSubDeviceIdsForOperations(int operationId, int deviceId) throws ApplicationManagementException;
void updateSubsStatus (int deviceId, int operationId, String status) throws ApplicationManagementException; void updateSubsStatus (int deviceId, int operationId, String status) throws ApplicationManagementException;

@ -60,6 +60,14 @@ public class EntAppReleaseWrapper {
@NotNull @NotNull
private String supportedOsVersions; private String supportedOsVersions;
@ApiModelProperty(name = "version",
value = "Version number of the applications installer specifically for windows")
private String version;
@ApiModelProperty(name = "packageName",
value = "PackageName of the application installer specifically for windows")
private String packageName;
public String getReleaseType() { public String getReleaseType() {
return releaseType; return releaseType;
} }
@ -99,4 +107,20 @@ public class EntAppReleaseWrapper {
public String getSupportedOsVersions() { return supportedOsVersions; } public String getSupportedOsVersions() { return supportedOsVersions; }
public void setSupportedOsVersions(String supportedOsVersions) { this.supportedOsVersions = supportedOsVersions; } public void setSupportedOsVersions(String supportedOsVersions) { this.supportedOsVersions = supportedOsVersions; }
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getPackageName() {
return packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
} }

@ -141,6 +141,7 @@ public class ApplicationManagerImpl implements ApplicationManager {
} }
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true); int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true);
ApplicationDTO applicationDTO = APIUtil.convertToAppDTO(applicationWrapper); ApplicationDTO applicationDTO = APIUtil.convertToAppDTO(applicationWrapper);
//uploading application artifacts //uploading application artifacts
ApplicationReleaseDTO applicationReleaseDTO = uploadEntAppReleaseArtifacts( ApplicationReleaseDTO applicationReleaseDTO = uploadEntAppReleaseArtifacts(
applicationDTO.getApplicationReleaseDTOs().get(0), applicationArtifact, applicationDTO.getApplicationReleaseDTOs().get(0), applicationArtifact,
@ -338,9 +339,25 @@ public class ApplicationManagerImpl implements ApplicationManager {
byte[] content = IOUtils.toByteArray(applicationArtifact.getInstallerStream()); byte[] content = IOUtils.toByteArray(applicationArtifact.getInstallerStream());
applicationReleaseDTO.setInstallerName(applicationArtifact.getInstallerName()); applicationReleaseDTO.setInstallerName(applicationArtifact.getInstallerName());
try (ByteArrayInputStream binary = new ByteArrayInputStream(content)) { try (ByteArrayInputStream binary = new ByteArrayInputStream(content)) {
ApplicationInstaller applicationInstaller = applicationStorageManager ApplicationInstaller applicationInstaller = null;
.getAppInstallerData(binary, deviceType); String packagename;
String packagename = applicationInstaller.getPackageName(); if (!DeviceTypes.WINDOWS.toString().equalsIgnoreCase(deviceType)) {
applicationInstaller = applicationStorageManager.getAppInstallerData(binary, deviceType);
packagename = applicationInstaller.getPackageName();
applicationReleaseDTO.setVersion(applicationInstaller.getVersion());
applicationReleaseDTO.setPackageName(packagename);
} else {
String windowsInstallerName = applicationArtifact.getInstallerName();
String extension = windowsInstallerName.substring(windowsInstallerName.lastIndexOf(".") + 1);
if (!extension.equalsIgnoreCase(Constants.MSI) &&
!extension.equalsIgnoreCase(Constants.APPX)) {
String msg = "Application Type doesn't match with supporting application types of " +
deviceType + "platform which are APPX and MSI";
log.error(msg);
throw new BadRequestException(msg);
}
packagename = applicationReleaseDTO.getPackageName();
}
try { try {
ConnectionManagerUtil.openDBConnection(); ConnectionManagerUtil.openDBConnection();
@ -354,8 +371,6 @@ public class ApplicationManagerImpl implements ApplicationManager {
log.error(msg); log.error(msg);
throw new ApplicationManagementException(msg); throw new ApplicationManagementException(msg);
} }
applicationReleaseDTO.setVersion(applicationInstaller.getVersion());
applicationReleaseDTO.setPackageName(packagename);
String md5OfApp = StorageManagementUtil.getMD5(new ByteArrayInputStream(content)); String md5OfApp = StorageManagementUtil.getMD5(new ByteArrayInputStream(content));
if (md5OfApp == null) { if (md5OfApp == null) {
String msg = "Error occurred while md5sum value retrieving process: application UUID " String msg = "Error occurred while md5sum value retrieving process: application UUID "
@ -1012,6 +1027,7 @@ public class ApplicationManagerImpl implements ApplicationManager {
log.error(msg); log.error(msg);
throw new BadRequestException(msg); throw new BadRequestException(msg);
} }
ApplicationReleaseDTO applicationReleaseDTO = uploadEntAppReleaseArtifacts( ApplicationReleaseDTO applicationReleaseDTO = uploadEntAppReleaseArtifacts(
APIUtil.releaseWrapperToReleaseDTO(entAppReleaseWrapper), applicationArtifact, deviceType.getName(), APIUtil.releaseWrapperToReleaseDTO(entAppReleaseWrapper), applicationArtifact, deviceType.getName(),
tenantId, true); tenantId, true);
@ -2680,7 +2696,12 @@ public class ApplicationManagerImpl implements ApplicationManager {
if (!StringUtils.isEmpty(entAppReleaseWrapper.getMetaData())) { if (!StringUtils.isEmpty(entAppReleaseWrapper.getMetaData())) {
applicationReleaseDTO.get().setMetaData(entAppReleaseWrapper.getMetaData()); applicationReleaseDTO.get().setMetaData(entAppReleaseWrapper.getMetaData());
} }
if (!StringUtils.isEmpty(entAppReleaseWrapper.getVersion())) { // Updating version
applicationReleaseDTO.get().setVersion(entAppReleaseWrapper.getVersion());
}
if (!StringUtils.isEmpty(entAppReleaseWrapper.getPackageName())) { // Updating packageName
applicationReleaseDTO.get().setPackageName(entAppReleaseWrapper.getPackageName());
}
if (!StringUtils.isEmpty(applicationArtifact.getInstallerName()) if (!StringUtils.isEmpty(applicationArtifact.getInstallerName())
&& applicationArtifact.getInstallerStream() != null) { && applicationArtifact.getInstallerStream() != null) {
DeviceType deviceTypeObj = APIUtil.getDeviceTypeData(applicationDTO.getDeviceTypeId()); DeviceType deviceTypeObj = APIUtil.getDeviceTypeData(applicationDTO.getDeviceTypeId());
@ -3114,6 +3135,17 @@ public class ApplicationManagerImpl implements ApplicationManager {
throw new BadRequestException(msg); throw new BadRequestException(msg);
} }
unrestrictedRoles = applicationWrapper.getUnrestrictedRoles(); unrestrictedRoles = applicationWrapper.getUnrestrictedRoles();
//Validating the version number and the packageName of the Windows applications
if (DeviceTypes.WINDOWS.toString().equalsIgnoreCase(applicationWrapper.getDeviceType())) {
if (applicationWrapper.getEntAppReleaseWrappers().get(0).getVersion() == null ||
applicationWrapper.getEntAppReleaseWrappers().get(0).getPackageName() == null) {
String msg = "Application Version number or/and PackageName both are required only when the app type is " +
applicationWrapper.getDeviceType() + " platform type";
log.error(msg);
throw new BadRequestException(msg);
}
}
} else if (param instanceof WebAppWrapper) { } else if (param instanceof WebAppWrapper) {
WebAppWrapper webAppWrapper = (WebAppWrapper) param; WebAppWrapper webAppWrapper = (WebAppWrapper) param;
appName = webAppWrapper.getName(); appName = webAppWrapper.getName();
@ -3326,6 +3358,15 @@ public class ApplicationManagerImpl implements ApplicationManager {
log.error(msg); log.error(msg);
throw new BadRequestException(msg); throw new BadRequestException(msg);
} }
//Validating the version number and the packageName of the Windows new applications releases
if (DeviceTypes.WINDOWS.toString().equalsIgnoreCase(deviceType)) {
if (entAppReleaseWrapper.get().getVersion() == null || entAppReleaseWrapper.get().getPackageName() == null) {
String msg = "Application Version number or/and PackageName..both are required only when the app type is " +
deviceType + " platform type";
log.error(msg);
throw new BadRequestException(msg);
}
}
} else if (param instanceof WebAppReleaseWrapper) { } else if (param instanceof WebAppReleaseWrapper) {
WebAppReleaseWrapper webAppReleaseWrapper = (WebAppReleaseWrapper) param; WebAppReleaseWrapper webAppReleaseWrapper = (WebAppReleaseWrapper) param;
UrlValidator urlValidator = new UrlValidator(); UrlValidator urlValidator = new UrlValidator();
@ -3414,18 +3455,31 @@ public class ApplicationManagerImpl implements ApplicationManager {
} }
} }
@Override
public boolean checkSubDeviceIdsForOperations(int operationId, int deviceId) throws ApplicationManagementException {
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId();
try {
ConnectionManagerUtil.openDBConnection();
List<Integer> deviceSubIds = subscriptionDAO.getDeviceSubIdsForOperation(operationId, deviceId, tenantId);
if (deviceSubIds.isEmpty() || deviceSubIds == null) {
return false;
}
} catch (ApplicationManagementDAOException e) {
String msg = "Error occurred while getting the device sub ids for the operations";
log.error(msg, e);
throw new ApplicationManagementException(msg, e);
} finally {
ConnectionManagerUtil.closeDBConnection();
}
return true;
}
@Override @Override
public void updateSubsStatus (int deviceId, int operationId, String status) throws ApplicationManagementException { public void updateSubsStatus (int deviceId, int operationId, String status) throws ApplicationManagementException {
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId();
try { try {
ConnectionManagerUtil.beginDBTransaction(); ConnectionManagerUtil.beginDBTransaction();
List<Integer> deviceSubIds = subscriptionDAO.getDeviceSubIdsForOperation(operationId, deviceId, tenantId); List<Integer> deviceSubIds = subscriptionDAO.getDeviceSubIdsForOperation(operationId, deviceId, tenantId);
if (deviceSubIds.isEmpty()){
ConnectionManagerUtil.rollbackDBTransaction();
String msg = "Couldn't find device subscription for operation id " + operationId;
log.error(msg);
throw new ApplicationManagementException(msg);
}
if (!subscriptionDAO.updateDeviceSubStatus(deviceId, deviceSubIds, status, tenantId)){ if (!subscriptionDAO.updateDeviceSubStatus(deviceId, deviceSubIds, status, tenantId)){
ConnectionManagerUtil.rollbackDBTransaction(); ConnectionManagerUtil.rollbackDBTransaction();
String msg = "Didn't update an any app subscription of device for operation Id: " + operationId; String msg = "Didn't update an any app subscription of device for operation Id: " + operationId;

@ -82,6 +82,7 @@ import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService;
import org.wso2.carbon.device.mgt.core.service.GroupManagementProviderService; import org.wso2.carbon.device.mgt.core.service.GroupManagementProviderService;
import org.wso2.carbon.device.mgt.core.util.MDMAndroidOperationUtil; import org.wso2.carbon.device.mgt.core.util.MDMAndroidOperationUtil;
import org.wso2.carbon.device.mgt.core.util.MDMIOSOperationUtil; import org.wso2.carbon.device.mgt.core.util.MDMIOSOperationUtil;
import org.wso2.carbon.device.mgt.core.util.MDMWindowsOperationUtil;
import org.wso2.carbon.identity.jwt.client.extension.dto.AccessTokenInfo; import org.wso2.carbon.identity.jwt.client.extension.dto.AccessTokenInfo;
import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.device.mgt.common.PaginationResult; import org.wso2.carbon.device.mgt.common.PaginationResult;
@ -1022,6 +1023,18 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
log.error(msg); log.error(msg);
throw new ApplicationManagementException(msg); throw new ApplicationManagementException(msg);
} }
} else if (DeviceTypes.WINDOWS.toString().equalsIgnoreCase(deviceType)) {
app.setType(mobileAppType);
app.setIdentifier(application.getPackageName());
app.setMetaData(application.getApplicationReleases().get(0).getMetaData());
app.setName(application.getInstallerName());
if (SubAction.INSTALL.toString().equalsIgnoreCase(action)) {
return MDMWindowsOperationUtil.createInstallAppOperation(app);
} else {
String msg = "Invalid Action is found. Action: " + action;
log.error(msg);
throw new ApplicationManagementException(msg);
}
} else { } else {
String msg = "Invalid device type is found. Device Type: " + deviceType; String msg = "Invalid device type is found. Device Type: " + deviceType;
log.error(msg); log.error(msg);

@ -304,6 +304,10 @@ public class APIUtil {
applicationReleaseDTO.setIsSharedWithAllTenants(entAppReleaseWrapper.getIsSharedWithAllTenants()); applicationReleaseDTO.setIsSharedWithAllTenants(entAppReleaseWrapper.getIsSharedWithAllTenants());
applicationReleaseDTO.setMetaData(entAppReleaseWrapper.getMetaData()); applicationReleaseDTO.setMetaData(entAppReleaseWrapper.getMetaData());
applicationReleaseDTO.setSupportedOsVersions(entAppReleaseWrapper.getSupportedOsVersions()); applicationReleaseDTO.setSupportedOsVersions(entAppReleaseWrapper.getSupportedOsVersions());
//Setting version number value specifically for windows type and in an instance of android and ios it will be null
applicationReleaseDTO.setVersion(entAppReleaseWrapper.getVersion());
//Setting package name value specifically for windows type and in an instance of android and ios it will be null
applicationReleaseDTO.setPackageName(entAppReleaseWrapper.getPackageName());
} else if (param instanceof WebAppReleaseWrapper){ } else if (param instanceof WebAppReleaseWrapper){
WebAppReleaseWrapper webAppReleaseWrapper = (WebAppReleaseWrapper) param; WebAppReleaseWrapper webAppReleaseWrapper = (WebAppReleaseWrapper) param;
applicationReleaseDTO.setDescription(webAppReleaseWrapper.getDescription()); applicationReleaseDTO.setDescription(webAppReleaseWrapper.getDescription());
@ -358,6 +362,7 @@ public class APIUtil {
application.setTags(applicationDTO.getTags()); application.setTags(applicationDTO.getTags());
application.setUnrestrictedRoles(applicationDTO.getUnrestrictedRoles()); application.setUnrestrictedRoles(applicationDTO.getUnrestrictedRoles());
application.setRating(applicationDTO.getAppRating()); application.setRating(applicationDTO.getAppRating());
application.setInstallerName(applicationDTO.getApplicationReleaseDTOs().get(0).getInstallerName());
List<ApplicationRelease> applicationReleases = new ArrayList<>(); List<ApplicationRelease> applicationReleases = new ArrayList<>();
if (ApplicationType.PUBLIC.toString().equals(applicationDTO.getType()) && application.getCategories() if (ApplicationType.PUBLIC.toString().equals(applicationDTO.getType()) && application.getCategories()
.contains("GooglePlaySyncedApp")) { .contains("GooglePlaySyncedApp")) {
@ -384,6 +389,7 @@ public class APIUtil {
applicationRelease.setDescription(applicationReleaseDTO.getDescription()); applicationRelease.setDescription(applicationReleaseDTO.getDescription());
applicationRelease.setVersion(applicationReleaseDTO.getVersion()); applicationRelease.setVersion(applicationReleaseDTO.getVersion());
applicationRelease.setPackageName(applicationReleaseDTO.getPackageName());
applicationRelease.setUuid(applicationReleaseDTO.getUuid()); applicationRelease.setUuid(applicationReleaseDTO.getUuid());
applicationRelease.setReleaseType(applicationReleaseDTO.getReleaseType()); applicationRelease.setReleaseType(applicationReleaseDTO.getReleaseType());
applicationRelease.setPrice(applicationReleaseDTO.getPrice()); applicationRelease.setPrice(applicationReleaseDTO.getPrice());

@ -65,6 +65,10 @@ public class Constants {
public static final String SUBSCRIBED = "SUBSCRIBED"; public static final String SUBSCRIBED = "SUBSCRIBED";
public static final String UNSUBSCRIBED = "UNSUBSCRIBED"; public static final String UNSUBSCRIBED = "UNSUBSCRIBED";
//App type constants related to window device type
public static final String MSI = "MSI";
public static final String APPX = "APPX";
private static final Map<String, String> AGENT_DATA = new HashMap<>(); private static final Map<String, String> AGENT_DATA = new HashMap<>();
static { static {
AGENT_DATA.put("android", "android-agent.apk"); AGENT_DATA.put("android", "android-agent.apk");

@ -373,6 +373,10 @@ public class ApplicationManagementPublisherAPIImpl implements ApplicationManagem
log.error("ApplicationDTO Creation Failed"); log.error("ApplicationDTO Creation Failed");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
} }
} catch (BadRequestException e) {
String msg = "Found incompatible payload with enterprise app release creating request.";
log.error(msg, e);
return Response.status(Response.Status.BAD_REQUEST).entity(msg).build();
} catch (ApplicationManagementException e) { } catch (ApplicationManagementException e) {
String msg = "Error occurred while creating the application"; String msg = "Error occurred while creating the application";
log.error(msg, e); log.error(msg, e);

@ -85,6 +85,12 @@
} }
}, },
"deviceTypes": { "deviceTypes": {
"mobileTypes": ["android", "ios"] "mobileTypes": ["android", "ios", "windows"]
},
"windowsDeviceType": {
"appType": ["msi", "appx"]
},
"windowsAppxMsiKeyValueForMetaData": {
"metaKeyArray": ["Package_Url", "Dependency_Package_Url", "Certificate_Hash", "Encoded_Cert_Content", "Package_Family_Name", "Product_Id", "Content_Uri", "File_Hash"]
} }
} }

@ -46,6 +46,7 @@ class NewAppDetailsForm extends React.Component {
categories: [], categories: [],
tags: [], tags: [],
deviceTypes: [], deviceTypes: [],
selectedValue: null,
fetching: false, fetching: false,
roleSearchValue: [], roleSearchValue: [],
unrestrictedRoles: [], unrestrictedRoles: [],
@ -312,12 +313,33 @@ class NewAppDetailsForm extends React.Component {
}); });
}; };
// Event handler for selecting the device type
handleSelect = event => {
this.setState({
selectedValue: event,
});
if (this.props.selectedValueHandler) {
this.props.selectedValueHandler(event);
}
};
// Event handler for selecting the windows app type
handleSelectForAppType = event => {
if (this.props.selectedAppTypeHandler) {
this.props.selectedAppTypeHandler(event);
}
};
render() { render() {
const config = this.props.context;
// Windows installation app types
const appTypes = config.windowsDeviceType.appType;
const { formConfig } = this.props; const { formConfig } = this.props;
const { const {
categories, categories,
tags, tags,
deviceTypes, deviceTypes,
selectedValue,
fetching, fetching,
unrestrictedRoles, unrestrictedRoles,
} = this.state; } = this.state;
@ -358,6 +380,7 @@ class NewAppDetailsForm extends React.Component {
<Select <Select
style={{ width: '100%' }} style={{ width: '100%' }}
placeholder="select device type" placeholder="select device type"
onSelect={this.handleSelect.bind(this)}
> >
{deviceTypes.map(deviceType => { {deviceTypes.map(deviceType => {
return ( return (
@ -396,6 +419,31 @@ class NewAppDetailsForm extends React.Component {
})(<Input placeholder="ex: Lorem App" />)} })(<Input placeholder="ex: Lorem App" />)}
</Form.Item> </Form.Item>
{/* App Type only shown for windows device types for enterprise apps */}
{selectedValue === 'windows' &&
this.props.formConfig.installationType === 'ENTERPRISE' && (
<Form.Item {...formItemLayout} label="App Type">
{getFieldDecorator('appType', {
rules: [
{
required: true,
message: 'Please select app type',
},
],
})(
<Select
style={{ width: '100%' }}
placeholder="select application type"
onSelect={this.handleSelectForAppType}
>
{appTypes.map(appType => {
return <Option key={appType}>{appType}</Option>;
})}
</Select>,
)}
</Form.Item>
)}
{/* description*/} {/* description*/}
<Form.Item {...formItemLayout} label="Description"> <Form.Item {...formItemLayout} label="Description">
{getFieldDecorator('description', { {getFieldDecorator('description', {

@ -33,6 +33,7 @@ import {
} from 'antd'; } from 'antd';
import '@babel/polyfill'; import '@babel/polyfill';
import Authorized from '../../../../../../../../components/Authorized/Authorized'; import Authorized from '../../../../../../../../components/Authorized/Authorized';
import { withConfigContext } from '../../../../../../../../components/ConfigContext';
const { Text } = Typography; const { Text } = Typography;
@ -59,6 +60,12 @@ function getBase64(file) {
}); });
} }
// function for access the full name of the binary file using the installation path
function extractBinaryFileName(installationPath) {
let UploadedBinaryName = installationPath.split('/');
return UploadedBinaryName[UploadedBinaryName.length - 1];
}
class NewAppUploadForm extends React.Component { class NewAppUploadForm extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -77,6 +84,7 @@ class NewAppUploadForm extends React.Component {
osVersionsHelperText: '', osVersionsHelperText: '',
osVersionsValidateStatus: 'validating', osVersionsValidateStatus: 'validating',
metaData: [], metaData: [],
appType: null,
}; };
this.lowerOsVersion = null; this.lowerOsVersion = null;
this.upperOsVersion = null; this.upperOsVersion = null;
@ -93,6 +101,8 @@ class NewAppUploadForm extends React.Component {
e.preventDefault(); e.preventDefault();
const { formConfig } = this.props; const { formConfig } = this.props;
const { specificElements } = formConfig; const { specificElements } = formConfig;
let windowsAppTypeMetaArray = [];
let metaValue = [];
this.props.form.validateFields((err, values) => { this.props.form.validateFields((err, values) => {
if (!err) { if (!err) {
@ -107,6 +117,19 @@ class NewAppUploadForm extends React.Component {
releaseType, releaseType,
} = values; } = values;
/**
* To save the metaData value that receive from
* metaData UI In an windows app type creation
*/
if (
((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows') ||
this.props.deviceType === 'windows') &&
this.state.metaData.length !== 0
) {
metaValue = [...this.state.metaData];
}
// add release data // add release data
const release = { const release = {
description: releaseDescription, description: releaseDescription,
@ -116,6 +139,90 @@ class NewAppUploadForm extends React.Component {
releaseType: releaseType, releaseType: releaseType,
}; };
const data = new FormData();
const config = this.props.context;
// Accessing the Meta Key value for windows device type from the config.json file
const metaKeyValues =
config.windowsAppxMsiKeyValueForMetaData.metaKeyArray;
/*
Setting up the app type specific values to the
metaData state field and Setting up the version
and the packageName for windows type
*/
if (
(this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows') ||
this.props.deviceType === 'windows'
) {
// Setting up the version and packageName
release.version = values.version;
release.packageName = values.packageName;
// setting the metaData value for appx type
if (
this.props.selectedAppType === 'appx' ||
this.state.appType === 'appx'
) {
windowsAppTypeMetaArray = [
{
key: metaKeyValues[0],
value: values.packageUrl,
},
{
key: metaKeyValues[1],
value: values.dependencyPackageUrl,
},
{
key: metaKeyValues[2],
value: values.certificateHash,
},
{
key: metaKeyValues[3],
value: values.encodedCertContent,
},
{
key: metaKeyValues[4],
value: values.packageName,
},
];
windowsAppTypeMetaArray = [
...windowsAppTypeMetaArray,
...metaValue,
];
} else if (
this.props.selectedAppType === 'msi' ||
this.state.appType === 'msi'
) {
windowsAppTypeMetaArray = [
{
key: metaKeyValues[5],
value: values.productId,
},
{
key: metaKeyValues[6],
value: values.contentUri,
},
{
key: metaKeyValues[7],
value: values.fileHash,
},
];
windowsAppTypeMetaArray = [
...windowsAppTypeMetaArray,
...metaValue,
];
}
this.setState(
{
metaData: windowsAppTypeMetaArray,
},
() => {
release.metaData = JSON.stringify(this.state.metaData);
this.props.onSuccessReleaseData({ data, release });
},
);
}
if (specificElements.hasOwnProperty('version')) { if (specificElements.hasOwnProperty('version')) {
release.version = values.version; release.version = values.version;
} }
@ -126,7 +233,6 @@ class NewAppUploadForm extends React.Component {
release.packageName = values.packageName; release.packageName = values.packageName;
} }
const data = new FormData();
let isFormValid = true; // flag to check if this form is valid let isFormValid = true; // flag to check if this form is valid
if ( if (
@ -187,7 +293,16 @@ class NewAppUploadForm extends React.Component {
if (specificElements.hasOwnProperty('binaryFile')) { if (specificElements.hasOwnProperty('binaryFile')) {
data.append('binaryFile', binaryFile[0].originFileObj); data.append('binaryFile', binaryFile[0].originFileObj);
} }
this.props.onSuccessReleaseData({ data, release }); // Condition to check is it not an Enterprise windows app creation or release
if (
!(
this.props.selectedValue === 'windows' &&
this.props.formConfig.installationType === 'ENTERPRISE'
) &&
this.props.deviceType !== 'windows'
) {
this.props.onSuccessReleaseData({ data, release });
}
} }
} }
}); });
@ -203,13 +318,44 @@ class NewAppUploadForm extends React.Component {
icons: fileList, icons: fileList,
}); });
}; };
handleBinaryFileChange = ({ fileList }) => { handleBinaryFileChange = ({ fileList }) => {
let validity = true;
// To set the app type of windows by using the binary file in an new app release
if (this.props.formConfig.isNewRelease && fileList.length !== 0) {
let firstUploadedBinaryFileName = extractBinaryFileName(
this.props.uploadedInstalltionAppType,
);
let FirstFileExtension = firstUploadedBinaryFileName.substr(
firstUploadedBinaryFileName.lastIndexOf('.') + 1,
);
let LastFileExtension = fileList[0].name.substr(
fileList[0].name.lastIndexOf('.') + 1,
);
if (FirstFileExtension !== LastFileExtension) {
validity = false;
} else if (LastFileExtension === 'msi' || LastFileExtension === 'appx') {
this.setState({
appType: LastFileExtension,
});
}
}
if (fileList.length === 1) { if (fileList.length === 1) {
this.setState({ this.setState({
binaryFileHelperText: '', binaryFileHelperText: '',
}); });
} }
this.setState({ binaryFiles: fileList });
if (validity) {
this.setState({
binaryFiles: fileList,
});
} else {
this.setState({
binaryFileHelperText: 'Upload Correct Binary File extension',
});
}
}; };
handleScreenshotChange = ({ fileList }) => { handleScreenshotChange = ({ fileList }) => {
@ -266,6 +412,7 @@ class NewAppUploadForm extends React.Component {
render() { render() {
const { formConfig, supportedOsVersions } = this.props; const { formConfig, supportedOsVersions } = this.props;
const { getFieldDecorator } = this.props.form; const { getFieldDecorator } = this.props.form;
const config = this.props.context;
const { const {
icons, icons,
screenshots, screenshots,
@ -399,7 +546,12 @@ class NewAppUploadForm extends React.Component {
</Text> </Text>
</Col> </Col>
</Row> </Row>
{formConfig.specificElements.hasOwnProperty('packageName') && (
{/* Package Name field for windows device type and other specific scene using it */}
{(formConfig.specificElements.hasOwnProperty('packageName') ||
(this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows') ||
this.props.deviceType === 'windows') && (
<Form.Item {...formItemLayout} label="Package Name"> <Form.Item {...formItemLayout} label="Package Name">
{getFieldDecorator('packageName', { {getFieldDecorator('packageName', {
rules: [ rules: [
@ -425,7 +577,11 @@ class NewAppUploadForm extends React.Component {
</Form.Item> </Form.Item>
)} )}
{formConfig.specificElements.hasOwnProperty('version') && ( {/* Version field for windows device type and other specific scene using it */}
{(formConfig.specificElements.hasOwnProperty('version') ||
(this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows') ||
this.props.deviceType === 'windows') && (
<Form.Item {...formItemLayout} label="Version"> <Form.Item {...formItemLayout} label="Version">
{getFieldDecorator('version', { {getFieldDecorator('version', {
rules: [ rules: [
@ -438,6 +594,127 @@ class NewAppUploadForm extends React.Component {
</Form.Item> </Form.Item>
)} )}
{/* Windows Appx App Type Fields */}
{/* For Windows appx app type only -> Package Url */}
{((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows' &&
this.props.selectedAppType === 'appx') ||
this.state.appType === 'appx') && (
<Form.Item {...formItemLayout} label="Package Url">
{getFieldDecorator('packageUrl', {
rules: [
{
required: true,
message: 'Please input the package url',
},
],
})(<Input placeholder="Package Url" />)}
</Form.Item>
)}
{/* For Windows appx app type only -> Dependency Package Url */}
{((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows' &&
this.props.selectedAppType === 'appx') ||
this.state.appType === 'appx') && (
<Form.Item {...formItemLayout} label="Dependency Package Url">
{getFieldDecorator('dependencyPackageUrl', {})(
<Input placeholder="Dependency Package Url" />,
)}
</Form.Item>
)}
{/* For Windows appx app type only -> Certificate Hash */}
{((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows' &&
this.props.selectedAppType === 'appx') ||
this.state.appType === 'appx') && (
<Form.Item {...formItemLayout} label="Certificate Hash">
{getFieldDecorator('certificateHash', {
rules: [
{
required: true,
message: 'Please input the certificate hash',
},
],
})(<Input placeholder="Certificate Hash" />)}
</Form.Item>
)}
{/* For Windows appx app type only -> Encoded Certificate Content */}
{((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows' &&
this.props.selectedAppType === 'appx') ||
this.state.appType === 'appx') && (
<Form.Item {...formItemLayout} label="Encoded Cert Content">
{getFieldDecorator('encodedCertContent', {
rules: [
{
required: true,
message: 'Give the encoded cert content',
},
],
})(
<TextArea
placeholder="Enter a encoded certificate content"
rows={5}
/>,
)}
</Form.Item>
)}
{/* Windows MSI App Type Fields */}
{/* For Windows msi app type only -> Product Id */}
{((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows' &&
this.props.selectedAppType === 'msi') ||
this.state.appType === 'msi') && (
<Form.Item {...formItemLayout} label="Product Id">
{getFieldDecorator('productId', {
rules: [
{
required: true,
message: 'Please input the product id',
},
],
})(<Input placeholder="Product Id" />)}
</Form.Item>
)}
{/* For Windows msi app type only -> Content URI */}
{((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows' &&
this.props.selectedAppType === 'msi') ||
this.state.appType === 'msi') && (
<Form.Item {...formItemLayout} label="Content URI">
{getFieldDecorator('contentUri', {
rules: [
{
required: true,
message: 'Please input the content uri',
},
],
})(<Input placeholder="Content Uri" />)}
</Form.Item>
)}
{/* For Windows msi app type only -> File Hash */}
{((this.props.formConfig.installationType === 'ENTERPRISE' &&
this.props.selectedValue === 'windows' &&
this.props.selectedAppType === 'msi') ||
this.state.appType === 'msi') && (
<Form.Item {...formItemLayout} label="File Hash">
{getFieldDecorator('fileHash', {
rules: [
{
required: true,
message: 'Please input the file hash',
},
],
})(<Input placeholder="File Hash" />)}
</Form.Item>
)}
<Form.Item {...formItemLayout} label="Release Type"> <Form.Item {...formItemLayout} label="Release Type">
{getFieldDecorator('releaseType', { {getFieldDecorator('releaseType', {
rules: [ rules: [
@ -547,49 +824,60 @@ class NewAppUploadForm extends React.Component {
{getFieldDecorator('meta', {})( {getFieldDecorator('meta', {})(
<div> <div>
{metaData.map((data, index) => { {metaData.map((data, index) => {
return ( /*
<InputGroup key={index}> Exclude showing the values related to
<Row gutter={8}> windows app type variables in meta Data UI
<Col span={5}> */
<Input if (
placeholder="key" !config.windowsAppxMsiKeyValueForMetaData.metaKeyArray.includes(
value={data.key} data.key,
onChange={e => { )
metaData[index].key = e.currentTarget.value; ) {
this.setState({ return (
metaData, <InputGroup key={index}>
}); <Row gutter={8}>
}} <Col span={5}>
/> <Input
</Col> placeholder="key"
<Col span={8}> value={data.key}
<Input onChange={e => {
placeholder="value" metaData[index].key = e.currentTarget.value;
value={data.value} this.setState({
onChange={e => { metaData,
metaData[index].value = e.currentTarget.value; });
this.setState({ }}
metaData, />
}); </Col>
}} <Col span={8}>
/> <Input
</Col> placeholder="value"
<Col span={3}> value={data.value}
<Button onChange={e => {
type="dashed" metaData[index].value =
shape="circle" e.currentTarget.value;
icon={<MinusOutlined />} this.setState({
onClick={() => { metaData,
metaData.splice(index, 1); });
this.setState({ }}
metaData, />
}); </Col>
}} <Col span={3}>
/> <Button
</Col> type="dashed"
</Row> shape="circle"
</InputGroup> icon={<MinusOutlined />}
); onClick={() => {
metaData.splice(index, 1);
this.setState({
metaData,
});
}}
/>
</Col>
</Row>
</InputGroup>
);
}
})} })}
<Button <Button
type="dashed" type="dashed"
@ -633,4 +921,6 @@ class NewAppUploadForm extends React.Component {
} }
} }
export default Form.create({ name: 'app-upload-form' })(NewAppUploadForm); export default withConfigContext(
Form.create({ name: 'app-upload-form' })(NewAppUploadForm),
);

@ -44,6 +44,8 @@ class AddNewAppFormComponent extends React.Component {
release: null, release: null,
isError: false, isError: false,
deviceType: null, deviceType: null,
selectedValue: null,
selectedAppType: null,
supportedOsVersions: [], supportedOsVersions: [],
errorText: '', errorText: '',
forbiddenErrors: { forbiddenErrors: {
@ -157,6 +159,20 @@ class AddNewAppFormComponent extends React.Component {
}); });
}; };
// For passing the device type as the prop for other component
selectedValueHandler = selectedValue => {
this.setState({
selectedValue,
});
};
// For passing the app type as the prop for other component
selectedAppTypeHandler = selectedAppType => {
this.setState({
selectedAppType,
});
};
render() { render() {
const { const {
loading, loading,
@ -164,6 +180,8 @@ class AddNewAppFormComponent extends React.Component {
isError, isError,
supportedOsVersions, supportedOsVersions,
errorText, errorText,
selectedValue,
selectedAppType,
} = this.state; } = this.state;
const { formConfig } = this.props; const { formConfig } = this.props;
return ( return (
@ -180,18 +198,21 @@ class AddNewAppFormComponent extends React.Component {
<div style={{ display: current === 0 ? 'unset' : 'none' }}> <div style={{ display: current === 0 ? 'unset' : 'none' }}>
<NewAppDetailsForm <NewAppDetailsForm
formConfig={formConfig} formConfig={formConfig}
selectedValueHandler={this.selectedValueHandler}
selectedAppTypeHandler={this.selectedAppTypeHandler}
onSuccessApplicationData={this.onSuccessApplicationData} onSuccessApplicationData={this.onSuccessApplicationData}
/> />
</div> </div>
<div style={{ display: current === 1 ? 'unset' : 'none' }}> <div style={{ display: current === 1 ? 'unset' : 'none' }}>
<NewAppUploadForm <NewAppUploadForm
formConfig={formConfig} formConfig={formConfig}
selectedValue={selectedValue}
selectedAppType={selectedAppType}
supportedOsVersions={supportedOsVersions} supportedOsVersions={supportedOsVersions}
onSuccessReleaseData={this.onSuccessReleaseData} onSuccessReleaseData={this.onSuccessReleaseData}
onClickBackButton={this.onClickBackButton} onClickBackButton={this.onClickBackButton}
/> />
</div> </div>
<div style={{ display: current === 2 ? 'unset' : 'none' }}> <div style={{ display: current === 2 ? 'unset' : 'none' }}>
{!isError && ( {!isError && (
<Result <Result

@ -27,6 +27,7 @@ import { handleApiError } from '../../../../../../services/utils/errorHandler';
import NewAppUploadForm from '../../../AddNewApp/components/AddNewAppForm/components/NewAppUploadForm'; import NewAppUploadForm from '../../../AddNewApp/components/AddNewAppForm/components/NewAppUploadForm';
const formConfig = { const formConfig = {
isNewRelease: true,
specificElements: { specificElements: {
binaryFile: { binaryFile: {
required: true, required: true,
@ -163,6 +164,11 @@ class AddNewReleaseFormComponent extends React.Component {
<NewAppUploadForm <NewAppUploadForm
forbiddenErrors={forbiddenErrors} forbiddenErrors={forbiddenErrors}
formConfig={formConfig} formConfig={formConfig}
deviceType={this.props.deviceType}
// Takes the first upload app type installation path
uploadedInstalltionAppType={
this.props.location.state.appDetails.installerPath
}
supportedOsVersions={supportedOsVersions} supportedOsVersions={supportedOsVersions}
onSuccessReleaseData={this.onSuccessReleaseData} onSuccessReleaseData={this.onSuccessReleaseData}
onClickBackButton={this.onClickBackButton} onClickBackButton={this.onClickBackButton}

@ -783,6 +783,7 @@ class AppDetailsDrawer extends React.Component {
to={{ to={{
pathname: `/publisher/apps/${app.deviceType}/${app.id}/add-release`, pathname: `/publisher/apps/${app.deviceType}/${app.id}/add-release`,
state: { state: {
appDetails: app.applicationReleases[0],
fullAppDetails: app.applicationReleases, fullAppDetails: app.applicationReleases,
}, },
}} }}

@ -65,6 +65,12 @@ function getBase64(file) {
reader.onerror = error => reject(error); reader.onerror = error => reject(error);
}); });
} }
// function for access the name of the binary file using the installation path
function extractBinaryFileName(installationPath) {
let UploadedBinaryName = installationPath.split('/');
let binaryFileName = UploadedBinaryName[UploadedBinaryName.length - 1];
return binaryFileName.substr(binaryFileName.lastIndexOf('.') + 1);
}
class EditReleaseModal extends React.Component { class EditReleaseModal extends React.Component {
// To add subscription type & tenancy sharing, refer https://gitlab.com/entgra/carbon-device-mgt/merge_requests/331 // To add subscription type & tenancy sharing, refer https://gitlab.com/entgra/carbon-device-mgt/merge_requests/331
@ -156,7 +162,6 @@ class EditReleaseModal extends React.Component {
const { formConfig } = this.state; const { formConfig } = this.state;
const { specificElements } = formConfig; const { specificElements } = formConfig;
let metaData = []; let metaData = [];
try { try {
metaData = JSON.parse(release.metaData); metaData = JSON.parse(release.metaData);
} catch (e) { } catch (e) {
@ -185,26 +190,34 @@ class EditReleaseModal extends React.Component {
}, },
}); });
} }
if (specificElements.hasOwnProperty('version')) { // Showing the packageName value in the edit form UI
if (
formConfig.specificElements.hasOwnProperty('packageName') ||
(this.props.type === 'ENTERPRISE' && this.props.deviceType === 'windows')
) {
this.props.form.setFields({ this.props.form.setFields({
version: { packageName: {
value: release.version, value: release.packageName,
}, },
}); });
} }
if (specificElements.hasOwnProperty('url')) { // Showing the version value in the edit form UI
if (
formConfig.specificElements.hasOwnProperty('version') ||
(this.props.type === 'ENTERPRISE' && this.props.deviceType === 'windows')
) {
this.props.form.setFields({ this.props.form.setFields({
url: { version: {
value: release.url, value: release.version,
}, },
}); });
} }
if (specificElements.hasOwnProperty('packageName')) { if (specificElements.hasOwnProperty('url')) {
this.props.form.setFields({ this.props.form.setFields({
packageName: { url: {
value: release.packageName, value: release.url,
}, },
}); });
} }
@ -248,6 +261,10 @@ class EditReleaseModal extends React.Component {
const { formConfig } = this.state; const { formConfig } = this.state;
const { specificElements } = formConfig; const { specificElements } = formConfig;
// Accessing the extension type of the current uploaded binary file
const appTypeExtension = extractBinaryFileName(
this.props.release.installerPath,
);
this.props.form.validateFields((err, values) => { this.props.form.validateFields((err, values) => {
if (!err) { if (!err) {
@ -280,10 +297,38 @@ class EditReleaseModal extends React.Component {
data.append('binaryFile', binaryFiles[0].originFileObj); data.append('binaryFile', binaryFiles[0].originFileObj);
} }
if (specificElements.hasOwnProperty('version')) { if (
specificElements.hasOwnProperty('version') ||
(this.props.type === 'ENTERPRISE' &&
this.props.deviceType === 'windows')
) {
release.version = values.version; release.version = values.version;
} }
// Accessing the Meta Key value for windows device type from the config.json file
const metaKeyValues =
config.windowsAppxMsiKeyValueForMetaData.metaKeyArray;
if (
specificElements.hasOwnProperty('packageName') ||
(this.props.type === 'ENTERPRISE' &&
this.props.deviceType === 'windows')
) {
release.packageName = values.packageName;
// Setting up the packageName to the package_Family_Name key in an appx app type instance
if (appTypeExtension === config.windowsDeviceType.appType[1]) {
let metaDataArray = this.state.metaData;
let filterMetaArray = metaDataArray.filter(
obj => obj.key !== metaKeyValues[4],
);
filterMetaArray.push({
key: metaKeyValues[4],
value: values.packageName,
});
release.metaData = JSON.stringify(filterMetaArray);
}
}
if (specificElements.hasOwnProperty('url')) { if (specificElements.hasOwnProperty('url')) {
release.url = values.url; release.url = values.url;
} }
@ -335,7 +380,6 @@ class EditReleaseModal extends React.Component {
message: 'Done!', message: 'Done!',
description: 'Saved!', description: 'Saved!',
}); });
// console.log(updatedRelease);
this.props.updateRelease(updatedRelease); this.props.updateRelease(updatedRelease);
} }
}) })
@ -463,7 +507,6 @@ class EditReleaseModal extends React.Component {
)} )}
</Form.Item> </Form.Item>
)} )}
{formConfig.specificElements.hasOwnProperty('url') && ( {formConfig.specificElements.hasOwnProperty('url') && (
<Form.Item {...formItemLayout} label="URL"> <Form.Item {...formItemLayout} label="URL">
{getFieldDecorator('url', { {getFieldDecorator('url', {
@ -477,19 +520,6 @@ class EditReleaseModal extends React.Component {
</Form.Item> </Form.Item>
)} )}
{formConfig.specificElements.hasOwnProperty('version') && (
<Form.Item {...formItemLayout} label="Version">
{getFieldDecorator('version', {
rules: [
{
required: true,
message: 'Please input the version',
},
],
})(<Input placeholder="Version" />)}
</Form.Item>
)}
<Form.Item {...formItemLayout} label="Icon"> <Form.Item {...formItemLayout} label="Icon">
{getFieldDecorator('icon', { {getFieldDecorator('icon', {
valuePropName: 'icon', valuePropName: 'icon',
@ -528,6 +558,38 @@ class EditReleaseModal extends React.Component {
)} )}
</Form.Item> </Form.Item>
{/* Package Name field for windows device type and other specific scene using it */}
{(formConfig.specificElements.hasOwnProperty('packageName') ||
(this.props.type === 'ENTERPRISE' &&
this.props.deviceType === 'windows')) && (
<Form.Item {...formItemLayout} label="Package Name">
{getFieldDecorator('packageName', {
rules: [
{
required: true,
message: 'Please input the package name',
},
],
})(<Input placeholder="Package Name" />)}
</Form.Item>
)}
{/* Version field for windows device type and other specific scene using it */}
{(formConfig.specificElements.hasOwnProperty('version') ||
(this.props.type === 'ENTERPRISE' &&
this.props.deviceType === 'windows')) && (
<Form.Item {...formItemLayout} label="Version">
{getFieldDecorator('version', {
rules: [
{
required: true,
message: 'Please input the version',
},
],
})(<Input placeholder="Version" />)}
</Form.Item>
)}
<Form.Item {...formItemLayout} label="Release Type"> <Form.Item {...formItemLayout} label="Release Type">
{getFieldDecorator('releaseType', { {getFieldDecorator('releaseType', {
rules: [ rules: [
@ -650,50 +712,59 @@ class EditReleaseModal extends React.Component {
})( })(
<div> <div>
{metaData.map((data, index) => { {metaData.map((data, index) => {
return ( if (
<InputGroup key={index}> !(
<Row gutter={8}> data.key ===
<Col span={10}> config.windowsAppxMsiKeyValueForMetaData
<Input .metaKeyArray[4]
placeholder="key" )
value={data.key} ) {
onChange={e => { return (
metaData[index].key = e.currentTarget.value; <InputGroup key={index}>
this.setState({ <Row gutter={8}>
metaData, <Col span={10}>
}); <Input
}} placeholder="key"
/> value={data.key}
</Col> onChange={e => {
<Col span={10}> metaData[index].key =
<Input e.currentTarget.value;
placeholder="value" this.setState({
value={data.value} metaData,
onChange={e => { });
metaData[index].value = }}
e.currentTarget.value; />
this.setState({ </Col>
metaData, <Col span={10}>
}); <Input
}} placeholder="value"
/> value={data.value}
</Col> onChange={e => {
<Col span={3}> metaData[index].value =
<Button e.currentTarget.value;
type="dashed" this.setState({
shape="circle" metaData,
icon={<MinusOutlined />} });
onClick={() => { }}
metaData.splice(index, 1); />
this.setState({ </Col>
metaData, <Col span={3}>
}); <Button
}} type="dashed"
/> shape="circle"
</Col> icon={<MinusOutlined />}
</Row> onClick={() => {
</InputGroup> metaData.splice(index, 1);
); this.setState({
metaData,
});
}}
/>
</Col>
</Row>
</InputGroup>
);
}
})} })}
<Button <Button
type="dashed" type="dashed"

@ -41,6 +41,8 @@ class ReleaseView extends React.Component {
const { app, release } = this.props; const { app, release } = this.props;
const config = this.props.context; const config = this.props.context;
const { lifecycle, currentLifecycleStatus } = this.props; const { lifecycle, currentLifecycleStatus } = this.props;
let isKeyInclude = false;
let metaArrayWithOutWindowsKey = [];
if (release == null) { if (release == null) {
return null; return null;
} }
@ -169,21 +171,31 @@ class ReleaseView extends React.Component {
<Text>META DATA</Text> <Text>META DATA</Text>
<Row> <Row>
{metaData.map((data, index) => { {metaData.map((data, index) => {
return ( // Exclude showing the values related to windows app type variables in the metaData UI
<Col if (
key={index} !config.windowsAppxMsiKeyValueForMetaData.metaKeyArray.includes(
lg={8} data.key,
md={6} )
xs={24} ) {
style={{ marginTop: 15 }} isKeyInclude = false;
> metaArrayWithOutWindowsKey.push(data);
<Text>{data.key}</Text> return (
<br /> <Col
<Text type="secondary">{data.value}</Text> key={index}
</Col> lg={8}
); md={6}
xs={24}
style={{ marginTop: 15 }}
>
<Text>{data.key}</Text>
<br />
<Text type="secondary">{data.value}</Text>
</Col>
);
}
})} })}
{metaData.length === 0 && ( {(metaData.length === 0 ||
(!isKeyInclude && metaArrayWithOutWindowsKey.length === 0)) && (
<Text type="secondary">No meta data available.</Text> <Text type="secondary">No meta data available.</Text>
)} )}
</Row> </Row>

@ -40,5 +40,8 @@
"color": "#008cc4", "color": "#008cc4",
"theme": "filled" "theme": "filled"
} }
},
"windowsAppxMsiKeyValueForMetaData": {
"metaKeyArray": ["Package_Url", "Dependency_Package_Url", "Certificate_Hash", "Encoded_Cert_Content", "Package_Family_Name", "Product_Id", "Content_Uri", "File_Hash"]
} }
} }

@ -148,6 +148,8 @@ class ReleaseView extends React.Component {
const config = this.props.context; const config = this.props.context;
const release = app.applicationReleases[0]; const release = app.applicationReleases[0];
let isKeyInclude = false;
let metaArrayWithOutWindowsKey = [];
let metaData = []; let metaData = [];
try { try {
metaData = JSON.parse(release.metaData); metaData = JSON.parse(release.metaData);
@ -270,24 +272,33 @@ class ReleaseView extends React.Component {
</div> </div>
<Divider /> <Divider />
<Text>META DATA</Text> <Text>META DATA</Text>
<Row> <Row>
{metaData.map((data, index) => { {metaData.map((data, index) => {
return ( if (
<Col !config.windowsAppxMsiKeyValueForMetaData.metaKeyArray.includes(
key={index} data.key,
lg={8} )
md={6} ) {
xs={24} isKeyInclude = false;
style={{ marginTop: 15 }} metaArrayWithOutWindowsKey.push(data);
> return (
<Text>{data.key}</Text> <Col
<br /> key={index}
<Text type="secondary">{data.value}</Text> lg={8}
</Col> md={6}
); xs={24}
style={{ marginTop: 15 }}
>
<Text>{data.key}</Text>
<br />
<Text type="secondary">{data.value}</Text>
</Col>
);
}
})} })}
{metaData.length === 0 && ( {(metaData.length === 0 ||
(!isKeyInclude &&
metaArrayWithOutWindowsKey.length === 0)) && (
<Text type="secondary">No meta data available.</Text> <Text type="secondary">No meta data available.</Text>
)} )}
</Row> </Row>

@ -19,7 +19,7 @@
package org.wso2.carbon.device.mgt.common; package org.wso2.carbon.device.mgt.common;
/** /**
* This class holds all the constants used for IOS and Android. * This class holds all the constants used for IOS, Android, Windows.
*/ */
public class MDMAppConstants { public class MDMAppConstants {
@ -49,6 +49,29 @@ public class MDMAppConstants {
public static final String OPCODE_UNINSTALL_APPLICATION = "UNINSTALL_APPLICATION"; public static final String OPCODE_UNINSTALL_APPLICATION = "UNINSTALL_APPLICATION";
} }
public class WindowsConstants {
private WindowsConstants() {
throw new AssertionError();
}
public static final String INSTALL_ENTERPRISE_APPLICATION = "INSTALL_ENTERPRISE_APPLICATION";
//App type constants related to window device type
public static final String MSI = "MSI";
public static final String APPX = "APPX";
//MSI Meta Key Constant
public static final String MSI_PRODUCT_ID = "Product_Id";
public static final String MSI_CONTENT_URI = "Content_Uri";
public static final String MSI_FILE_HASH = "File_Hash";
//APPX Meta Key Constant
public static final String APPX_PACKAGE_URI = "Package_Url";
public static final String APPX_DEPENDENCY_PACKAGE_URL = "Dependency_Package_Url";
public static final String APPX_CERTIFICATE_HASH = "Certificate_Hash";
public static final String APPX_ENCODED_CERT_CONTENT = "Encoded_Cert_Content";
public static final String APPX_PACKAGE_FAMILY_NAME = "Package_Family_Name";
}
public class RegistryConstants { public class RegistryConstants {
private RegistryConstants() { private RegistryConstants() {

@ -62,6 +62,10 @@ public class App {
private String location; private String location;
@ApiModelProperty(name = "properties", value = "List of meta data.", required = true) @ApiModelProperty(name = "properties", value = "List of meta data.", required = true)
private Properties properties; private Properties properties;
@ApiModelProperty(name = "metaData",
value = "Meta data of the application release",
required = true)
private String metaData;
public MobileAppTypes getType() { public MobileAppTypes getType() {
return type; return type;
@ -142,4 +146,8 @@ public class App {
public void setProperties(Properties properties) { public void setProperties(Properties properties) {
this.properties = properties; this.properties = properties;
} }
public String getMetaData() { return metaData; }
public void setMetaData(String metaData) { this.metaData = metaData; }
} }

@ -0,0 +1,54 @@
/*
* 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.app.mgt.windows;
import com.google.gson.Gson;
import java.io.Serializable;
/**
* This class represents the Windows Enterprise App Types information.
*/
public class EnterpriseApplication implements Serializable {
private HostedAppxApplication hostedAppxApplication;
private HostedMSIApplication hostedMSIApplication;
public HostedAppxApplication getHostedAppxApplication() {
return hostedAppxApplication;
}
public void setHostedAppxApplication(HostedAppxApplication hostedAppxApplication) {
this.hostedAppxApplication = hostedAppxApplication;
}
public HostedMSIApplication getHostedMSIApplication() {
return hostedMSIApplication;
}
public void setHostedMSIApplication(HostedMSIApplication hostedMSIApplication) {
this.hostedMSIApplication = hostedMSIApplication;
}
public String toJSON() {
Gson gson = new Gson();
return gson.toJson(this);
}
}

@ -0,0 +1,71 @@
/*
* 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.app.mgt.windows;
import java.util.List;
public class HostedAppxApplication {
private String packageUri;
private String packageFamilyName;
private List<String> dependencyPackageUri;
private String certificateHash;
private String encodedCertificate;
public String getPackageUri() {
return packageUri;
}
public void setPackageUri(String packageUri) {
this.packageUri = packageUri;
}
public String getPackageFamilyName() {
return packageFamilyName;
}
public void setPackageFamilyName(String packageFamilyName) {
this.packageFamilyName = packageFamilyName;
}
public List<String> getDependencyPackageUri() {
return dependencyPackageUri;
}
public void setDependencyPackageUri(List<String> dependencyPackageUri) {
this.dependencyPackageUri = dependencyPackageUri;
}
public String getCertificateHash() {
return certificateHash;
}
public void setCertificateHash(String certificateHash) {
this.certificateHash = certificateHash;
}
public String getEncodedCertificate() {
return encodedCertificate;
}
public void setEncodedCertificate(String encodedCertificate) {
this.encodedCertificate = encodedCertificate;
}
}

@ -0,0 +1,52 @@
/*
* 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.app.mgt.windows;
public class HostedMSIApplication {
private String productId;
private String contentUrl;
private String fileHash;
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String getContentUrl() {
return contentUrl;
}
public void setContentUrl(String contentUrl) {
this.contentUrl = contentUrl;
}
public String getFileHash() {
return fileHash;
}
public void setFileHash(String fileHash) {
this.fileHash = fileHash;
}
}

@ -0,0 +1,146 @@
/*
* 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.core.util;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.MDMAppConstants;
import org.wso2.carbon.device.mgt.common.app.mgt.App;
import org.wso2.carbon.device.mgt.common.app.mgt.windows.EnterpriseApplication;
import org.wso2.carbon.device.mgt.common.app.mgt.windows.HostedAppxApplication;
import org.wso2.carbon.device.mgt.common.app.mgt.windows.HostedMSIApplication;
import org.wso2.carbon.device.mgt.common.exceptions.UnknownApplicationTypeException;
import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
import org.wso2.carbon.device.mgt.core.operation.mgt.ProfileOperation;
import java.util.ArrayList;
import java.util.List;
/**
* This class contains the all the operations related to Windows.
*/
public class MDMWindowsOperationUtil {
private static final Log log = LogFactory.getLog(MDMWindowsOperationUtil.class);
/**
* This method is used to create Install Authentication operation.
*
* @param application MobileApp application
* @return operation object
* @throws UnknownApplicationTypeException
*/
public static Operation createInstallAppOperation(App application) throws UnknownApplicationTypeException {
ProfileOperation operation = new ProfileOperation();
operation.setCode(MDMAppConstants.WindowsConstants.INSTALL_ENTERPRISE_APPLICATION);
operation.setType(Operation.Type.PROFILE);
String appType = windowsAppType(application.getName());
String metaData = application.getMetaData();
JsonArray metaJsonArray = jsonStringToArray(metaData);
switch (application.getType()) {
case ENTERPRISE:
EnterpriseApplication enterpriseApplication = new EnterpriseApplication();
if (appType.equalsIgnoreCase(MDMAppConstants.WindowsConstants.APPX)) {
HostedAppxApplication hostedAppxApplication = new HostedAppxApplication();
List<String> dependencyPackageList = new ArrayList<>();
for (int i = 0; i < metaJsonArray.size(); i++) {
JsonElement metaElement = metaJsonArray.get(i);
JsonObject metaObject = metaElement.getAsJsonObject();
if (MDMAppConstants.WindowsConstants.APPX_PACKAGE_URI.equals(metaObject.get("key").getAsString())) {
hostedAppxApplication.setPackageUri(metaObject.get("value").getAsString().trim());
}
else if (MDMAppConstants.WindowsConstants.APPX_PACKAGE_FAMILY_NAME.equals(metaObject.get("key").getAsString())) {
hostedAppxApplication.setPackageFamilyName(metaObject.get("value").getAsString().trim());
}
else if (MDMAppConstants.WindowsConstants.APPX_DEPENDENCY_PACKAGE_URL.equals(metaObject.get("key").getAsString())
&& metaObject.has("value")) {
dependencyPackageList.add(metaObject.get("value").getAsString().trim());
hostedAppxApplication.setDependencyPackageUri(dependencyPackageList);
}
else if (MDMAppConstants.WindowsConstants.APPX_CERTIFICATE_HASH.equals(metaObject.get("key").getAsString())
&& metaObject.has("value")) {
hostedAppxApplication.setCertificateHash(metaObject.get("value").getAsString().trim());
}
else if (MDMAppConstants.WindowsConstants.APPX_ENCODED_CERT_CONTENT.equals(metaObject.get("key").getAsString())
&& metaObject.has("value")) {
hostedAppxApplication.setEncodedCertificate(metaObject.get("value").getAsString().trim());
}
}
enterpriseApplication.setHostedAppxApplication(hostedAppxApplication);
} else if (appType.equalsIgnoreCase(MDMAppConstants.WindowsConstants.MSI)) {
HostedMSIApplication hostedMSIApplication = new HostedMSIApplication();
for (int i = 0; i < metaJsonArray.size(); i++) {
JsonElement metaElement = metaJsonArray.get(i);
JsonObject metaObject = metaElement.getAsJsonObject();
if (MDMAppConstants.WindowsConstants.MSI_PRODUCT_ID.equals(metaObject.get("key").getAsString())) {
hostedMSIApplication.setProductId(metaObject.get("value").getAsString().trim());
}
else if (MDMAppConstants.WindowsConstants.MSI_CONTENT_URI.equals(metaObject.get("key").getAsString())) {
hostedMSIApplication.setContentUrl(metaObject.get("value").getAsString().trim());
}
else if (MDMAppConstants.WindowsConstants.MSI_FILE_HASH.equals(metaObject.get("key").getAsString())) {
hostedMSIApplication.setFileHash(metaObject.get("value").getAsString().trim());
}
}
enterpriseApplication.setHostedMSIApplication(hostedMSIApplication);
}
operation.setPayLoad(enterpriseApplication.toJSON());
break;
default:
String msg = "Application type " + application.getType() + " is not supported";
log.error(msg);
throw new UnknownApplicationTypeException(msg);
}
return operation;
}
/**
* Method to get the installer file extension type for windows type apps(either appx or msi)
*
* @param installerName of the app type
* @return string extension of the windows app types(either appx or msi)
*/
public static String windowsAppType(String installerName) {
String extension = installerName.substring(installerName.lastIndexOf(".") + 1);
return extension;
}
/**
* Method to convert Json String to Json Array
*
* @param metaData metaData string array object containing the windows app type specific parameters
* @return the metaData Json String as Json Array
*/
public static JsonArray jsonStringToArray(String metaData) {
JsonArray metaJsonArray = new JsonParser().parse(metaData).getAsJsonArray();
return metaJsonArray;
}
}
Loading…
Cancel
Save