Merge branch 'pr/temp/feature/subscription/status/change' into 'master'

API to change application subscription status

See merge request entgra/carbon-device-mgt!868
feature/traccar-sync
Pahansith Gunathilake 3 years ago
commit 236d356a26

@ -23,6 +23,7 @@ import java.sql.Timestamp;
public class DeviceSubscriptionData { public class DeviceSubscriptionData {
private int subId;
private String action; private String action;
private long actionTriggeredTimestamp; private long actionTriggeredTimestamp;
private String actionTriggeredBy; private String actionTriggeredBy;
@ -82,4 +83,12 @@ public class DeviceSubscriptionData {
public String getCurrentInstalledVersion() { return currentInstalledVersion; } public String getCurrentInstalledVersion() { return currentInstalledVersion; }
public void setCurrentInstalledVersion(String currentInstalledVersion) { this.currentInstalledVersion = currentInstalledVersion; } public void setCurrentInstalledVersion(String currentInstalledVersion) { this.currentInstalledVersion = currentInstalledVersion; }
public int getSubId() {
return subId;
}
public void setSubId(int subId) {
this.subId = subId;
}
} }

@ -0,0 +1,32 @@
package io.entgra.application.mgt.common;
public class OperationStatusBean {
private int operationId;
private String status;
private String operationCode;
public int getOperationId() {
return operationId;
}
public void setOperationId(int operationId) {
this.operationId = operationId;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getOperationCode() {
return operationCode;
}
public void setOperationCode(String operationCode) {
this.operationCode = operationCode;
}
}

@ -327,7 +327,9 @@ public interface ApplicationManager {
*/ */
boolean checkSubDeviceIdsForOperations(int operationId, int deviceId) throws ApplicationManagementException; boolean checkSubDeviceIdsForOperations(int operationId, int deviceId) throws ApplicationManagementException;
void updateSubsStatus (int deviceId, int operationId, String status) throws ApplicationManagementException; void updateSubStatus(int deviceId, List<Integer> operationId, String status) throws ApplicationManagementException;
void updateSubsStatus(int deviceId, int operationId, String status) throws ApplicationManagementException;
/** /**

@ -34,6 +34,16 @@ import java.util.Properties;
*/ */
public interface SubscriptionManager { public interface SubscriptionManager {
/**
* Use to update status of a subscription
*
* @param deviceId Id of the device
* @param subId subscription id
* @param status status to be changed
*/
void updateSubscriptionStatus(int deviceId, int subId, String status)
throws SubscriptionManagementException;
/** /**
* Performs bulk subscription operation for a given application and a subscriber list. * Performs bulk subscription operation for a given application and a subscriber list.
* @param applicationUUID UUID of the application to subscribe/unsubscribe * @param applicationUUID UUID of the application to subscribe/unsubscribe

@ -101,6 +101,10 @@ public interface SubscriptionDAO {
List<Integer> getDeviceSubIds(List<Integer> deviceIds, int applicationReleaseId, int tenantId) List<Integer> getDeviceSubIds(List<Integer> deviceIds, int applicationReleaseId, int tenantId)
throws ApplicationManagementDAOException; throws ApplicationManagementDAOException;
int getDeviceIdForSubId(int subId, int tenantId) throws ApplicationManagementDAOException;
List<Integer> getOperationIdsForSubId(int subId, int tenantId) throws ApplicationManagementDAOException;
List<Integer> getDeviceSubIdsForOperation(int operationId, int deviceID, int tenantId) List<Integer> getDeviceSubIdsForOperation(int operationId, int deviceID, int tenantId)
throws ApplicationManagementDAOException; throws ApplicationManagementDAOException;

@ -691,6 +691,68 @@ public class GenericSubscriptionDAOImpl extends AbstractDAOImpl implements Subsc
} }
} }
@Override
public int getDeviceIdForSubId(int subId, int tenantId) throws ApplicationManagementDAOException {
try {
Connection conn = this.getDBConnection();
String sql = "SELECT DM_DEVICE_ID "
+ "FROM AP_DEVICE_SUBSCRIPTION "
+ "WHERE ID = ? AND "
+ "TENANT_ID = ?";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, subId);
stmt.setInt(2, tenantId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return rs.getInt("DM_DEVICE_ID");
}
}
return -1;
}
} catch (DBConnectionException e) {
String msg = "Error occurred while obtaining the DB connection to get app operation ids for given "
+ "subscription id.";
log.error(msg, e);
throw new ApplicationManagementDAOException(msg, e);
} catch (SQLException e) {
String msg = "Error occurred when processing SQL to get operation ids for given subscription id.";
log.error(msg, e);
throw new ApplicationManagementDAOException(msg, e);
}
}
@Override
public List<Integer> getOperationIdsForSubId(int subId, int tenantId) throws ApplicationManagementDAOException {
try {
Connection conn = this.getDBConnection();
List<Integer> operationIds = new ArrayList<>();
String sql = "SELECT OPERATION_ID "
+ "FROM AP_APP_SUB_OP_MAPPING "
+ "WHERE AP_DEVICE_SUBSCRIPTION_ID = ? AND "
+ "TENANT_ID = ?";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, subId);
stmt.setInt(2, tenantId);
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
operationIds.add(rs.getInt("OPERATION_ID"));
}
}
return operationIds;
}
} catch (DBConnectionException e) {
String msg = "Error occurred while obtaining the DB connection to get app operation ids for given "
+ "subscription id.";
log.error(msg, e);
throw new ApplicationManagementDAOException(msg, e);
} catch (SQLException e) {
String msg = "Error occurred when processing SQL to get operation ids for given subscription id.";
log.error(msg, e);
throw new ApplicationManagementDAOException(msg, e);
}
}
@Override @Override
public List<Integer> getDeviceSubIdsForOperation(int operationId, int deviceId, int tenantId) public List<Integer> getDeviceSubIdsForOperation(int operationId, int deviceId, int tenantId)
throws ApplicationManagementDAOException { throws ApplicationManagementDAOException {

@ -3668,7 +3668,40 @@ ApplicationManagerImpl implements ApplicationManager {
} }
@Override @Override
public void updateSubsStatus (int deviceId, int operationId, String status) throws ApplicationManagementException { public void updateSubStatus(int deviceId, List<Integer> operationIds, String status) throws ApplicationManagementException {
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId();
try {
ConnectionManagerUtil.beginDBTransaction();
for (int operationId : operationIds) {
List<Integer> deviceSubIds = subscriptionDAO.getDeviceSubIdsForOperation(operationId, deviceId, tenantId);
if (!subscriptionDAO.updateDeviceSubStatus(deviceId, deviceSubIds, status, tenantId)){
ConnectionManagerUtil.rollbackDBTransaction();
String msg = "Didn't update an any app subscription of device for operation Id: " + operationId;
log.error(msg);
throw new ApplicationManagementException(msg);
}
}
ConnectionManagerUtil.commitDBTransaction();
} catch (ApplicationManagementDAOException e) {
String msg = "Error occured while updating app subscription status of the device.";
log.error(msg, e);
throw new ApplicationManagementException(msg, e);
} catch (DBConnectionException e) {
String msg = "Error occurred while obersving the database connection to update aoo subscription status of "
+ "device.";
log.error(msg, e);
throw new ApplicationManagementException(msg, e);
} catch (TransactionManagementException e) {
String msg = "Error occurred while executing database transaction";
log.error(msg, e);
throw new ApplicationManagementException(msg, e);
} finally {
ConnectionManagerUtil.closeDBConnection();
}
}
@Override
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();

@ -20,6 +20,7 @@ package io.entgra.application.mgt.core.impl;
import com.google.gson.Gson; import com.google.gson.Gson;
import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity; import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@ -1189,18 +1190,62 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
} }
} }
public int installEnrollmentApplications(ApplicationPolicyDTO applicationPolicyDTO) @Override
throws ApplicationManagementException { public void updateSubscriptionStatus(int deviceId, int subId, String status)
throws SubscriptionManagementException {
try {
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true);
List<Integer> operationIds = getOperationIdsForSubId(subId, tenantId);
APIUtil.getApplicationManager().updateSubStatus(deviceId, operationIds, status);
} catch (DBConnectionException e) {
String msg = "Error occurred while observing the database connection to get operation Ids for " + subId;
log.error(msg, e);
throw new SubscriptionManagementException(msg, e);
} catch (ApplicationManagementException e) {
String msg = "Error occurred while updating subscription status with the id: "
+ subId;
log.error(msg, e);
throw new SubscriptionManagementException(msg, e);
}
}
HttpClient httpClient; private List<Integer> getOperationIdsForSubId(int subId, int tenantId) throws SubscriptionManagementException {
PostMethod request;
try { try {
ConnectionManagerUtil.openDBConnection();
return subscriptionDAO.getOperationIdsForSubId(subId, tenantId);
} catch (ApplicationManagementDAOException e) {
String msg = "Error occurred while getting operation Ids for subId" + subId;
log.error(msg, e);
throw new SubscriptionManagementException(msg, e);
} catch (DBConnectionException e) {
String msg = "Error occurred while observing the database connection to get operation Ids for " + subId;
log.error(msg, e);
throw new SubscriptionManagementException(msg, e);
} finally {
ConnectionManagerUtil.closeDBConnection();
}
}
private int invokeIOTCoreAPI(HttpMethodBase request) throws UserStoreException, APIManagerException, IOException {
HttpClient httpClient;
String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
ApiApplicationKey apiApplicationKey = OAuthUtils.getClientCredentials(tenantDomain); ApiApplicationKey apiApplicationKey = OAuthUtils.getClientCredentials(tenantDomain);
String username = String username =
PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm().getRealmConfiguration() PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm().getRealmConfiguration()
.getAdminUserName() + Constants.ApplicationInstall.AT + tenantDomain; .getAdminUserName() + Constants.ApplicationInstall.AT + tenantDomain;
AccessTokenInfo tokenInfo = OAuthUtils.getOAuthCredentials(apiApplicationKey, username); AccessTokenInfo tokenInfo = OAuthUtils.getOAuthCredentials(apiApplicationKey, username);
request.addRequestHeader(Constants.ApplicationInstall.AUTHORIZATION,
Constants.ApplicationInstall.AUTHORIZATION_HEADER_VALUE + tokenInfo.getAccessToken());
httpClient = new HttpClient();
httpClient.executeMethod(request);
return request.getStatusCode();
}
public int installEnrollmentApplications(ApplicationPolicyDTO applicationPolicyDTO)
throws ApplicationManagementException {
PostMethod request;
try {
String requestUrl = Constants.ApplicationInstall.ENROLLMENT_APP_INSTALL_PROTOCOL + System String requestUrl = Constants.ApplicationInstall.ENROLLMENT_APP_INSTALL_PROTOCOL + System
.getProperty(Constants.ApplicationInstall.IOT_CORE_HOST) + Constants.ApplicationInstall.COLON .getProperty(Constants.ApplicationInstall.IOT_CORE_HOST) + Constants.ApplicationInstall.COLON
+ System.getProperty(Constants.ApplicationInstall.IOT_CORE_PORT) + System.getProperty(Constants.ApplicationInstall.IOT_CORE_PORT)
@ -1210,14 +1255,9 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
StringRequestEntity requestEntity = new StringRequestEntity(payload, MediaType.APPLICATION_JSON, StringRequestEntity requestEntity = new StringRequestEntity(payload, MediaType.APPLICATION_JSON,
Constants.ApplicationInstall.ENCODING); Constants.ApplicationInstall.ENCODING);
httpClient = new HttpClient();
request = new PostMethod(requestUrl); request = new PostMethod(requestUrl);
request.addRequestHeader(Constants.ApplicationInstall.AUTHORIZATION,
Constants.ApplicationInstall.AUTHORIZATION_HEADER_VALUE + tokenInfo.getAccessToken());
request.setRequestEntity(requestEntity); request.setRequestEntity(requestEntity);
httpClient.executeMethod(request); return invokeIOTCoreAPI(request);
return request.getStatusCode();
} catch (UserStoreException e) { } catch (UserStoreException e) {
String msg = "Error while accessing user store for user with Android device."; String msg = "Error while accessing user store for user with Android device.";
log.error(msg, e); log.error(msg, e);
@ -1240,6 +1280,13 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
} }
} }
private String getIOTCoreBaseUrl() {
return Constants.HTTPS_PROTOCOL + Constants.SCHEME_SEPARATOR + System
.getProperty(Constants.IOT_CORE_HOST) + Constants.COLON
+ System.getProperty(Constants.IOT_CORE_HTTPS_PORT);
}
@Override @Override
public PaginationResult getAppInstalledDevices(PaginationRequest request, String appUUID) public PaginationResult getAppInstalledDevices(PaginationRequest request, String appUUID)
throws ApplicationManagementException { throws ApplicationManagementException {
@ -1430,6 +1477,7 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
} }
deviceSubscriptionData.setActionType(subscription.getActionTriggeredFrom()); deviceSubscriptionData.setActionType(subscription.getActionTriggeredFrom());
deviceSubscriptionData.setStatus(subscription.getStatus()); deviceSubscriptionData.setStatus(subscription.getStatus());
deviceSubscriptionData.setSubId(subscription.getId());
deviceSubscriptionDataList.add(deviceSubscriptionData); deviceSubscriptionDataList.add(deviceSubscriptionData);
break; break;
} }

@ -44,7 +44,11 @@ public class Constants {
public static final String IOT_CORE_HTTPS_PORT = "iot.core.https.port"; public static final String IOT_CORE_HTTPS_PORT = "iot.core.https.port";
public static final String HTTPS_PROTOCOL = "https"; public static final String HTTPS_PROTOCOL = "https";
public static final String HTTP_PROTOCOL = "http"; public static final String HTTP_PROTOCOL = "http";
public static final String SCHEME_SEPARATOR = "://";
public static final String OPERATION_STATUS_UPDATE_API_BASE = "/api/device-mgt/v1.0/devices";
public static final String OPERATION_STATUS_UPDATE_API_URI = "/operation";
public static final String COLON = ":";
public static final String FORWARD_SLASH = "/"; public static final String FORWARD_SLASH = "/";
public static final String ANY = "ANY"; public static final String ANY = "ANY";
public static final String DEFAULT_PCK_NAME = "default.app.com"; public static final String DEFAULT_PCK_NAME = "default.app.com";

@ -0,0 +1,43 @@
package io.entgra.application.mgt.store.api.beans;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* This is used to map the status of subscription.
*/
@ApiModel(
value = "SubscriptionStatusBean",
description = "This class carries all information related map statuses of the subscription."
)
public class SubscriptionStatusBean {
@ApiModelProperty(
name = "sub id",
value = "Subscription Id.",
required = true
)
private int subId;
@ApiModelProperty(
name = "status",
value = "Status of the subscription.",
required = true
)
private String status;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public int getSubId() {
return subId;
}
public void setSubId(int subId) {
this.subId = subId;
}
}

@ -17,6 +17,7 @@
*/ */
package io.entgra.application.mgt.store.api.services.admin; package io.entgra.application.mgt.store.api.services.admin;
import io.entgra.application.mgt.store.api.beans.SubscriptionStatusBean;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiParam;
@ -34,6 +35,7 @@ import io.entgra.application.mgt.common.ErrorResponse;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
@ -69,6 +71,13 @@ import java.util.List;
key = "perm:admin:app:subscription:view", key = "perm:admin:app:subscription:view",
roles = {"Internal/devicemgt-admin"}, roles = {"Internal/devicemgt-admin"},
permissions = {"/app-mgt/store/admin/subscription/view"} permissions = {"/app-mgt/store/admin/subscription/view"}
),
@Scope(
name = "View Application Subscriptions",
description = "View Application Subscriptions.",
key = "perm:admin:app:subscription:modify",
roles = {"Internal/devicemgt-admin"},
permissions = {"/app-mgt/store/admin/subscription/modify"}
) )
} }
) )
@ -79,6 +88,51 @@ public interface SubscriptionManagementAdminAPI {
String SCOPE = "scope"; String SCOPE = "scope";
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Path("/device/{deviceId}/status")
@ApiOperation(
consumes = MediaType.APPLICATION_JSON,
produces = MediaType.APPLICATION_JSON,
httpMethod = "PUT",
value = "Update subscription status",
notes = "This will update the subscription status that belongs to the given device id",
tags = "Subscription Management",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = SCOPE, value = "perm:admin:app:subscription:modify")
})
}
)
@ApiResponses(
value = {
@ApiResponse(
code = 200,
message = "OK. \n Successfully updated subscription status.",
response = List.class,
responseContainer = "List"),
@ApiResponse(
code = 404,
message = "Not Found. \n No Application found which has application release of UUID.",
response = ErrorResponse.class),
@ApiResponse(
code = 500,
message = "Internal Server Error. \n Error occurred while updating subscription status",
response = ErrorResponse.class)
})
Response updateSubscription(
@ApiParam(
name = "deviceId",
value = "Id of the device",
required = true)
@PathParam("deviceId") int deviceId,
@ApiParam(
name = "subscription status change bean",
value = "this bean contains the information related to status change",
required = true)
SubscriptionStatusBean subscriptionStatusBean
);
@GET @GET
@Path("/{uuid}") @Path("/{uuid}")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)

@ -18,6 +18,8 @@
package io.entgra.application.mgt.store.api.services.impl.admin; package io.entgra.application.mgt.store.api.services.impl.admin;
import io.entgra.application.mgt.common.exception.SubscriptionManagementException;
import io.entgra.application.mgt.store.api.beans.SubscriptionStatusBean;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -34,10 +36,12 @@ import org.wso2.carbon.device.mgt.common.PaginationResult;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue; import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam; import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.List; import java.util.List;
@ -50,6 +54,30 @@ public class SubscriptionManagementAdminAPIImpl implements SubscriptionManagemen
private static final Log log = LogFactory.getLog(SubscriptionManagementAdminAPIImpl.class); private static final Log log = LogFactory.getLog(SubscriptionManagementAdminAPIImpl.class);
@Override
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Path("/device/{deviceId}/status")
public Response updateSubscription(
@PathParam("deviceId") int deviceId,
SubscriptionStatusBean subscriptionStatusBean
) {
try {
RequestValidationUtil.validateSubscriptionStatus(subscriptionStatusBean.getStatus());
SubscriptionManager subscriptionManager = APIUtil.getSubscriptionManager();
subscriptionManager.updateSubscriptionStatus(deviceId, subscriptionStatusBean.getSubId(),
subscriptionStatusBean.getStatus());
return Response.status(Response.Status.OK).entity("Subscription status updated successfully").build();
} catch (BadRequestException e) {
return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
} catch (SubscriptionManagementException e) {
String msg = "Error occurred while changing subscription status of the subscription with the id "
+ subscriptionStatusBean.getSubId();
log.error(msg, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
}
}
@GET @GET
@Consumes("application/json") @Consumes("application/json")
@Produces("application/json") @Produces("application/json")

@ -18,12 +18,16 @@
*/ */
package io.entgra.application.mgt.store.api.services.impl.util; package io.entgra.application.mgt.store.api.services.impl.util;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import io.entgra.application.mgt.core.exception.BadRequestException; import io.entgra.application.mgt.core.exception.BadRequestException;
import io.entgra.application.mgt.store.api.util.Constants; import io.entgra.application.mgt.store.api.util.Constants;
import org.wso2.carbon.device.mgt.common.operation.mgt.ActivityStatus;
import java.util.List; import java.util.List;
import java.util.StringJoiner;
public class RequestValidationUtil { public class RequestValidationUtil {
@ -113,4 +117,18 @@ public class RequestValidationUtil {
throw new BadRequestException(msg); throw new BadRequestException(msg);
} }
} }
/**
* Checks if user requested subscription status is valid.
*
*/
public static void validateSubscriptionStatus(String status) throws BadRequestException{
if (!EnumUtils.isValidEnum(ActivityStatus.Status.class, status)) {
List<ActivityStatus.Status> validStatuses = EnumUtils.getEnumList(ActivityStatus.Status.class);
String validStatusesString = StringUtils.join(validStatuses, " | ");
String msg = "Invalid status type: " + status + ". \nValid status types are " + validStatusesString;
log.error(msg);
throw new BadRequestException(msg);
}
}
} }

@ -49,6 +49,7 @@
<Scope>perm:admin:app:publisher:update</Scope> <Scope>perm:admin:app:publisher:update</Scope>
<Scope>perm:admin:app:review:update</Scope> <Scope>perm:admin:app:review:update</Scope>
<Scope>perm:admin:app:subscription:view</Scope> <Scope>perm:admin:app:subscription:view</Scope>
<Scope>perm:admin:app:subscription:modify</Scope>
<Scope>perm:device-types:types</Scope> <Scope>perm:device-types:types</Scope>
<Scope>perm:enterprise:modify</Scope> <Scope>perm:enterprise:modify</Scope>
<Scope>perm:enterprise:view</Scope> <Scope>perm:enterprise:view</Scope>

@ -67,6 +67,7 @@
<Scope>perm:admin:app:publisher:update</Scope> <Scope>perm:admin:app:publisher:update</Scope>
<Scope>perm:admin:app:review:update</Scope> <Scope>perm:admin:app:review:update</Scope>
<Scope>perm:admin:app:subscription:view</Scope> <Scope>perm:admin:app:subscription:view</Scope>
<Scope>perm:admin:app:subscription:modify</Scope>
<Scope>perm:device-types:types</Scope> <Scope>perm:device-types:types</Scope>
<Scope>perm:enterprise:modify</Scope> <Scope>perm:enterprise:modify</Scope>
<Scope>perm:enterprise:view</Scope> <Scope>perm:enterprise:view</Scope>

Loading…
Cancel
Save