From aa4b62328367f8af68d6752d10cc4a65afc8f282 Mon Sep 17 00:00:00 2001 From: rajitha Date: Fri, 11 Aug 2023 09:50:33 +0530 Subject: [PATCH 1/5] Add JIT base provision and enrollment handlers --- .../jaxrs/beans/InvitationMailProfile.java | 28 ++ .../jaxrs/beans/JITEnrollmentInvitation.java | 50 ++++ .../service/api/UserManagementService.java | 73 ++++-- .../impl/UserManagementServiceImpl.java | 76 +++++- .../JITEnrollmentCallbackHandler.java | 219 ++++++++++++++++ .../interceptor/JITEnrollmentHandler.java | 90 +++++++ .../JITProvisionCallbackHandler.java | 72 +++++ .../interceptor/JITProvisionHandler.java | 248 ++++++++++++++++++ .../ui/request/interceptor/beans/JITData.java | 68 +++++ .../interceptor/beans/JITEnrollmentData.java | 68 +++++ .../exceptions/JITEnrollmentException.java | 30 +++ .../exceptions/JITProvisionException.java | 29 ++ .../interceptor/util/HandlerConstants.java | 8 + .../src/main/resources/conf/jit-config.xml | 28 ++ .../default-jit-enrollment-invitation.vm | 61 +++++ .../src/main/resources/p2.inf | 1 + .../src/main/resources/p2.inf | 1 + 17 files changed, 1132 insertions(+), 18 deletions(-) create mode 100644 components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/beans/InvitationMailProfile.java create mode 100644 components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/beans/JITEnrollmentInvitation.java create mode 100644 components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentCallbackHandler.java create mode 100644 components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentHandler.java create mode 100644 components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionCallbackHandler.java create mode 100644 components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java create mode 100644 components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/beans/JITData.java create mode 100644 components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/beans/JITEnrollmentData.java create mode 100644 components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/exceptions/JITEnrollmentException.java create mode 100644 components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/exceptions/JITProvisionException.java create mode 100644 features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/jit-config.xml create mode 100644 features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/email/templates/default-jit-enrollment-invitation.vm diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/beans/InvitationMailProfile.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/beans/InvitationMailProfile.java new file mode 100644 index 0000000000..4133d3f25e --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/beans/InvitationMailProfile.java @@ -0,0 +1,28 @@ +package io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +@ApiModel(value = "InvitationMailProfile", description = "Holds data related to JIT Enrollment invitation mails") +public class InvitationMailProfile { + @ApiModelProperty(name = "username", value = "Username (same as username in external IDP)", required = true) + private String username; + @ApiModelProperty(name = "mail", value = "Mail will be sent to this mail address", required = true) + private String mail; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getMail() { + return mail; + } + + public void setMail(String mail) { + this.mail = mail; + } +} diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/beans/JITEnrollmentInvitation.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/beans/JITEnrollmentInvitation.java new file mode 100644 index 0000000000..32e4bfbe9d --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/beans/JITEnrollmentInvitation.java @@ -0,0 +1,50 @@ +package io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.util.List; + +@ApiModel(value = "JITEnrollmentInvitation", description = "Holds data related to JIT enrollment invitations") +public class JITEnrollmentInvitation { + @ApiModelProperty(name = "mailProfiles", value = "Mail profiles to send mail invitations", required = true) + private List mailProfiles; + @ApiModelProperty(name = "ownershipType", value = "Ownership type of the enrollment", required = true) + private String ownershipType; + @ApiModelProperty(name = "deviceType", value = "Device type", required = true) + private String deviceType; + @ApiModelProperty(name = "sp", value = "Service provider name", required = true) + private String sp; + + public List getMailProfiles() { + return mailProfiles; + } + + public void setMailProfiles(List mailProfiles) { + this.mailProfiles = mailProfiles; + } + + public String getOwnershipType() { + return ownershipType; + } + + public void setOwnershipType(String ownershipType) { + this.ownershipType = ownershipType; + } + + public String getDeviceType() { + return deviceType; + } + + public void setDeviceType(String deviceType) { + this.deviceType = deviceType; + } + + public String getSp() { + return sp; + } + + public void setSp(String sp) { + this.sp = sp; + } +} diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/api/UserManagementService.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/api/UserManagementService.java index 2b7b81b72f..2f824ef34b 100644 --- a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/api/UserManagementService.java +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/api/UserManagementService.java @@ -18,33 +18,34 @@ package io.entgra.device.mgt.core.device.mgt.api.jaxrs.service.api; import com.google.gson.JsonArray; -import io.swagger.annotations.SwaggerDefinition; -import io.swagger.annotations.Info; -import io.swagger.annotations.ExtensionProperty; -import io.swagger.annotations.Extension; -import io.swagger.annotations.Tag; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.ResponseHeader; -import org.apache.axis2.transport.http.HTTPConstants; -import io.entgra.device.mgt.core.apimgt.annotations.Scopes; import io.entgra.device.mgt.core.apimgt.annotations.Scope; -import io.entgra.device.mgt.core.device.mgt.common.invitation.mgt.DeviceEnrollmentInvitation; +import io.entgra.device.mgt.core.apimgt.annotations.Scopes; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.ActivityList; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.BasicUserInfo; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.BasicUserInfoList; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.Credential; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.EnrollmentInvitation; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.ErrorResponse; +import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.JITEnrollmentInvitation; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.OldPasswordResetWrapper; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.PermissionList; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.RoleList; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.UserInfo; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.UserStoreList; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.util.Constants; +import io.entgra.device.mgt.core.device.mgt.common.invitation.mgt.DeviceEnrollmentInvitation; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Extension; +import io.swagger.annotations.ExtensionProperty; +import io.swagger.annotations.Info; +import io.swagger.annotations.ResponseHeader; +import io.swagger.annotations.SwaggerDefinition; +import io.swagger.annotations.Tag; +import org.apache.axis2.transport.http.HTTPConstants; import javax.validation.Valid; import javax.ws.rs.Consumes; @@ -54,7 +55,6 @@ import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; -import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; @@ -940,6 +940,49 @@ public interface UserManagementService { required = true) @Valid EnrollmentInvitation enrollmentInvitation); + @POST + @Path("/jit-enrollment-invite") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = HTTPConstants.HEADER_POST, + value = "Sending Enrollment Invitations to email address", + notes = "Send the a mail inviting recipients to enroll devices.", + tags = "User Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:users:send-invitation") + }) + } + ) + @ApiResponses(value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully sent the invitation mail."), + @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.\n", + response = ErrorResponse.class), + @ApiResponse( + code = 415, + message = "Unsupported media type. \n The format of the requested entity was not supported.\n", + response = ErrorResponse.class), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n " + + "Server error occurred while updating the user credentials.", + response = ErrorResponse.class) + }) + Response inviteExternalUsers( + @ApiParam( + name = "jitEnrollmentInvitation", + value = "List of email address of recipients", + required = true) + @Valid JITEnrollmentInvitation jitEnrollmentInvitation); + @POST @Path("/validate") Response validateUser(Credential credential); diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/UserManagementServiceImpl.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/UserManagementServiceImpl.java index cd8063fdca..2ed0d17bee 100644 --- a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/UserManagementServiceImpl.java +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/UserManagementServiceImpl.java @@ -19,7 +19,6 @@ package io.entgra.device.mgt.core.device.mgt.api.jaxrs.service.impl; import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import io.entgra.device.mgt.core.device.mgt.common.Device; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -28,7 +27,6 @@ import org.eclipse.wst.common.uriresolver.internal.util.URIEncoder; import org.wso2.carbon.context.CarbonContext; import org.wso2.carbon.context.PrivilegedCarbonContext; import io.entgra.device.mgt.core.device.mgt.common.exceptions.DeviceManagementException; -import io.entgra.device.mgt.core.device.mgt.common.EnrolmentInfo; import io.entgra.device.mgt.core.device.mgt.common.configuration.mgt.ConfigurationManagementException; import io.entgra.device.mgt.core.device.mgt.common.exceptions.OTPManagementException; import io.entgra.device.mgt.core.device.mgt.common.invitation.mgt.DeviceEnrollmentInvitation; @@ -45,6 +43,8 @@ import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.BasicUserInfoWrapper import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.Credential; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.EnrollmentInvitation; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.ErrorResponse; +import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.InvitationMailProfile; +import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.JITEnrollmentInvitation; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.OldPasswordResetWrapper; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.PermissionList; import io.entgra.device.mgt.core.device.mgt.api.jaxrs.beans.RoleList; @@ -83,7 +83,6 @@ import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; -import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; @@ -92,6 +91,7 @@ import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; +import java.nio.file.NoSuchFileException; import java.security.SecureRandom; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -757,6 +757,54 @@ public class UserManagementServiceImpl implements UserManagementService { return Response.status(Response.Status.OK).entity("Invitation mails have been sent.").build(); } + @POST + @Path("jit-enrollment-invite") + @Override + public Response inviteExternalUsers(JITEnrollmentInvitation jitEnrollmentInvitation) { + if (jitEnrollmentInvitation.getMailProfiles() == null || jitEnrollmentInvitation.getMailProfiles().isEmpty()) { + String msg = "Error occurred while validating mail profiles. Mail profiles cannot be empty"; + log.error(msg); + throw new BadRequestException( + new ErrorResponse.ErrorResponseBuilder().setMessage(msg).setCode(HttpStatus.SC_BAD_REQUEST). + build()); + } + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + String inviteBy = DeviceMgtAPIUtils.getAuthenticatedUser(); + try { + DeviceManagementProviderService dms = DeviceMgtAPIUtils.getDeviceManagementService(); + for (InvitationMailProfile mailProfile : jitEnrollmentInvitation.getMailProfiles()) { + Properties props = new Properties(); + props.setProperty("username", mailProfile.getUsername()); + props.setProperty("tenant-domain", tenantDomain); + props.setProperty("sp", jitEnrollmentInvitation.getSp()); + props.setProperty("ownership-type", jitEnrollmentInvitation.getOwnershipType()); + props.setProperty("device-type", jitEnrollmentInvitation.getDeviceType()); + props.setProperty("invite-by", inviteBy); + Set recipients = new HashSet<>(); + recipients.add(mailProfile.getMail()); + EmailMetaInfo metaInfo = new EmailMetaInfo(recipients, props); + dms.sendEnrolmentInvitation(getTemplateName(jitEnrollmentInvitation.getDeviceType(), + "jit-enrollment-invitation", "-"), metaInfo); + } + } catch (DeviceManagementException ex) { + String msg = "Error occurred while inviting user to enroll their device"; + log.error(msg, ex); + return Response.serverError().entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); + } catch (ConfigurationManagementException ex) { + String msg = "Error occurred while sending the email invitations. Mail server not configured."; + log.error(msg, ex); + return Response.serverError().entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); + } catch (NoSuchFileException ex) { + String msg = "Error occurred while retrieving email template"; + log.error(msg, ex); + return Response.serverError().entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); + } + return Response.status(Response.Status.OK).entity("Invitation mails have been sent.").build(); + } + @POST @Path("/validate") @Override @@ -1179,6 +1227,28 @@ public class UserManagementServiceImpl implements UserManagementService { return DeviceManagementConstants.EmailAttributes.DEFAULT_ENROLLMENT_TEMPLATE; } + private String getTemplateName(String deviceType, String prefix, String separator) throws NoSuchFileException { + String templateName = deviceType + separator + prefix; + File template = new File(CarbonUtils.getCarbonHome() + File.separator + "repository" + File.separator + + "resources" + File.separator + "email-templates" + File.separator + templateName + ".vm"); + if (template.exists()) { + return templateName; + } + String defaultTemplateName = "default" + separator + prefix; + File defaultTemplate = new File(CarbonUtils.getCarbonHome() + File.separator + "repository" + File.separator + + "resources" + File.separator + "email-templates" + File.separator + defaultTemplateName + ".vm"); + + if (defaultTemplate.exists()) { + if (log.isDebugEnabled()) { + log.debug("The template that is expected to use is not available. Therefore, using default template."); + } + return defaultTemplateName; + } + + String msg = "Didn't found template file for " + templateName; + throw new NoSuchFileException(msg); + } + /** * Searches users which matches a given filter based on a claim * diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentCallbackHandler.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentCallbackHandler.java new file mode 100644 index 0000000000..3d314bf79d --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentCallbackHandler.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.entgra.device.mgt.core.ui.request.interceptor; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.entgra.device.mgt.core.ui.request.interceptor.beans.AuthData; +import io.entgra.device.mgt.core.ui.request.interceptor.beans.JITData; +import io.entgra.device.mgt.core.ui.request.interceptor.beans.JITEnrollmentData; +import io.entgra.device.mgt.core.ui.request.interceptor.beans.ProxyResponse; +import io.entgra.device.mgt.core.ui.request.interceptor.exceptions.JITEnrollmentException; +import io.entgra.device.mgt.core.ui.request.interceptor.util.HandlerConstants; +import io.entgra.device.mgt.core.ui.request.interceptor.util.HandlerUtil; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpHeaders; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.util.Base64; + +@WebServlet( + name = "JIT Enrollment callback handler", + description = "Call token endpoint and retrieve token", + urlPatterns = { + "/jit-enrollment-callback" + } +) +public class JITEnrollmentCallbackHandler extends HttpServlet { + private static final Log log = LogFactory.getLog(JITEnrollmentCallbackHandler.class); + private String gatewayUrl; + private String keyManagerUrl; + private JITData JITInfo; + private String encodedClientCredentials; + private String applicationName; + private String clientId; + private String clientSecret; + private String scope; + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) { + gatewayUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + + System.getProperty(HandlerConstants.IOT_GW_HOST_ENV_VAR) + + HandlerConstants.COLON + HandlerUtil.getGatewayPort(request.getScheme()); + keyManagerUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + + System.getProperty(HandlerConstants.IOT_KM_HOST_ENV_VAR) + + HandlerConstants.COLON + HandlerUtil.getKeyManagerPort(request.getScheme()); + HttpSession session = request.getSession(false); + try { + if (session == null) { + response.sendError(HttpStatus.SC_UNAUTHORIZED); + return; + } + + JITInfo = (JITData) session.getAttribute(HandlerConstants.SESSION_JIT_DATA_KEY); + if (JITInfo == null) { + response.sendError(HttpStatus.SC_UNAUTHORIZED); + return; + } + + JITEnrollmentData JITEnrollmentInfo = (JITEnrollmentData) + session.getAttribute(HandlerConstants.SESSION_JIT_ENROLLMENT_DATA_KEY); + if (JITEnrollmentInfo == null) { + response.sendError(HttpStatus.SC_UNAUTHORIZED); + return; + } + applicationName = request.getContextPath().substring(1, + request.getContextPath().indexOf("-ui-request-handler")); + scope = "perm:metadata:view perm:metadata:create perm:metadata:update perm:android:enroll " + + "perm:device:enroll perm:android:view-configuration"; + populateApplicationData(registerApplication()); + persistAuthData(session, getToken()); + response.sendRedirect(JITEnrollmentInfo.getRedirectUrl() + "?ownershipType=" + + JITEnrollmentInfo.getOwnershipType() + "&os=" + JITEnrollmentInfo.getOs() + "&username=" + + JITEnrollmentInfo.getUsername() + "&tenantDomain=" + JITEnrollmentInfo.getTenantDomain()); + } catch (JITEnrollmentException | IOException ex) { + log.error("Error occurred while processing JIT provisioning callback request", ex); + } + } + + /*** + * Parse string data and build json object + * @param data - Json string + * @return {@link JsonObject} Json object corresponding to provided json string + * @throws JITEnrollmentException throws when error occurred while parsing + */ + private JsonObject parseResponseData(String data) throws JITEnrollmentException { + JsonParser parser = new JsonParser(); + JsonElement responseData = parser.parse(data); + if (responseData.isJsonObject()) { + return responseData.getAsJsonObject(); + } + throw new JITEnrollmentException("Unexpected response body return"); + } + + /*** + * Build application registration request + * @return {@link HttpPost} Application registration request + */ + private HttpPost buildApplicationRegistrationRequest() { + HttpPost applicationRegistrationRequest = new HttpPost(gatewayUrl + HandlerConstants.APP_REG_ENDPOINT); + applicationRegistrationRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + + JITInfo.getEncodedClientCredentials()); + applicationRegistrationRequest.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); + JsonArray tags = new JsonArray(); + tags.add("device_management"); + JsonObject payload = new JsonObject(); + payload.addProperty("applicationName", applicationName); + payload.add("tags", tags); + payload.addProperty("allowedToAllDomains", false); + payload.addProperty("mappingAnExistingOAuthApp", false); + applicationRegistrationRequest.setEntity(new StringEntity(payload.toString(), ContentType.APPLICATION_JSON)); + return applicationRegistrationRequest; + } + + /*** + * Populate dynamic client's data + * @param application - application data receiving from dcr request + */ + private void populateApplicationData(JsonObject application) { + clientId = application.get("client_id").getAsString(); + clientSecret = application.get("client_secret").getAsString(); + String headerValue = clientId+ ':' + clientSecret; + encodedClientCredentials = Base64.getEncoder().encodeToString(headerValue.getBytes()); + } + + /*** + * Register client application + * @return {@link JsonObject} Json object contain registered application data + * @throws JITEnrollmentException throws when error occurred while application registration + */ + private JsonObject registerApplication() throws JITEnrollmentException { + try { + ProxyResponse proxyResponse = HandlerUtil.execute(buildApplicationRegistrationRequest()); + if (proxyResponse.getCode() == HttpStatus.SC_CREATED || + proxyResponse.getCode() == HttpStatus.SC_OK) { + return parseResponseData(proxyResponse.getData()); + } + throw new JITEnrollmentException("Unexpected response status return for application registration request"); + } catch (IOException ex) { + throw new JITEnrollmentException("Error occurred while executing application registration request", ex); + } + } + + /*** + * Acquire token + * @return {@link JsonObject} Json object containing token data + * @throws JITEnrollmentException throws when error occurred while acquiring token + */ + private JsonObject getToken() throws JITEnrollmentException { + try { + ProxyResponse proxyResponse = HandlerUtil.execute(buildTokenAcquireRequest()); + if (proxyResponse.getCode() == org.apache.http.HttpStatus.SC_CREATED || + proxyResponse.getCode() == org.apache.http.HttpStatus.SC_OK) { + return parseResponseData(proxyResponse.getData()); + } + throw new JITEnrollmentException("Unexpected response status return for token acquiring request"); + } catch (IOException ex) { + throw new JITEnrollmentException("Error occurred while executing token acquiring request", ex); + } + } + + /*** + * Build token acquire request + * @return {@link HttpPost} Token acquire request + */ + private HttpPost buildTokenAcquireRequest() { + HttpPost tokenAcquiringRequest = new HttpPost(keyManagerUrl + HandlerConstants.OAUTH2_TOKEN_ENDPOINT); + tokenAcquiringRequest.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString()); + tokenAcquiringRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + + encodedClientCredentials); + StringEntity payload = new StringEntity( + "grant_type=" + HandlerConstants.CLIENT_CREDENTIAL_GRANT_TYPE + "&scope=" + scope, + ContentType.APPLICATION_FORM_URLENCODED); + tokenAcquiringRequest.setEntity(payload); + return tokenAcquiringRequest; + } + + /*** + * Persists auth data in session + * @param session - {@link HttpSession} + * @param token - Json object containing token data + */ + private void persistAuthData(HttpSession session, JsonObject token) { + AuthData authData = new AuthData(); + authData.setAccessToken(token.get("access_token").getAsString()); + authData.setClientId(clientId); + authData.setClientSecret(clientSecret); + authData.setEncodedClientApp(encodedClientCredentials); + authData.setScope(token.get("scope").getAsString()); + session.setAttribute(HandlerConstants.SESSION_AUTH_DATA_KEY, authData); + } +} diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentHandler.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentHandler.java new file mode 100644 index 0000000000..b13d4d904a --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentHandler.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.entgra.device.mgt.core.ui.request.interceptor; + +import io.entgra.device.mgt.core.ui.request.interceptor.beans.JITEnrollmentData; +import io.entgra.device.mgt.core.ui.request.interceptor.util.HandlerConstants; +import io.entgra.device.mgt.core.ui.request.interceptor.util.HandlerUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; + +@WebServlet( + name = "JIT enrollment handler", + description = "Handle jit enrollment request", + urlPatterns = { + "/jit-enrollment" + } +) +public class JITEnrollmentHandler extends HttpServlet { + private static final Log log = LogFactory.getLog(JITEnrollmentHandler.class); + private String username; + private String ownershipType; + private String os; + private String redirectUrl; + private String tenantDomain; + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) { + try { + HttpSession session = request.getSession(true); + String JITProvisionHandlerUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + + System.getProperty(HandlerConstants.IOT_CORE_HOST_ENV_VAR) + + HandlerConstants.COLON + HandlerUtil.getCorePort(request.getScheme()) + + request.getContextPath() + + HandlerConstants.JIT_PROVISION_HANDLER; + String onCompletionUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + + System.getProperty(HandlerConstants.IOT_CORE_HOST_ENV_VAR) + + HandlerConstants.COLON + HandlerUtil.getCorePort(request.getScheme()) + + request.getContextPath() + + "/jit-enrollment-callback"; + username = request.getParameter("username"); + ownershipType = request.getParameter("ownershipType"); + os = request.getParameter("os"); + redirectUrl = request.getParameter("redirectUrl"); + tenantDomain = request.getParameter("tenantDomain"); + String sp = request.getParameter("sp"); + persistJITData(session); + response.sendRedirect(JITProvisionHandlerUrl + "?tenantDomain=" + tenantDomain + + "&sp=" + sp + "&redirectUrl=" + onCompletionUrl); + } catch (IOException ex) { + log.error("Error occurred while handling JIT enrollment request"); + } + } + + /*** + * Persists JIT data in session + * @param session - {@link HttpSession} + */ + private void persistJITData(HttpSession session) { + JITEnrollmentData JITEnrollmentInfo = new JITEnrollmentData(); + JITEnrollmentInfo.setOwnershipType(ownershipType); + JITEnrollmentInfo.setOs(os); + JITEnrollmentInfo.setUsername(username); + JITEnrollmentInfo.setRedirectUrl(redirectUrl); + JITEnrollmentInfo.setTenantDomain(tenantDomain); + session.setAttribute(HandlerConstants.SESSION_JIT_ENROLLMENT_DATA_KEY, JITEnrollmentInfo); + } +} diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionCallbackHandler.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionCallbackHandler.java new file mode 100644 index 0000000000..0214069df0 --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionCallbackHandler.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.entgra.device.mgt.core.ui.request.interceptor; + +import io.entgra.device.mgt.core.ui.request.interceptor.beans.JITData; +import io.entgra.device.mgt.core.ui.request.interceptor.beans.JITEnrollmentData; +import io.entgra.device.mgt.core.ui.request.interceptor.util.HandlerConstants; +import io.entgra.device.mgt.core.ui.request.interceptor.util.HandlerUtil; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; + +@WebServlet( + name = "JIT callback handler", + description = "Call token endpoint and retrieve token", + urlPatterns = { + "/jit-provision-callback" + } +) +public class JITProvisionCallbackHandler extends HttpServlet { + private static final Log log = LogFactory.getLog(JITProvisionCallbackHandler.class); + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) { + HttpSession session = request.getSession(false); + String JITProvisionCallbackURL = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + + System.getProperty(HandlerConstants.IOT_CORE_HOST_ENV_VAR) + + HandlerConstants.COLON + HandlerUtil.getCorePort(request.getScheme()) + + request.getContextPath() + + HandlerConstants.JIT_PROVISION_CALLBACK_URL; + try { + if (session == null) { + response.sendError(HttpStatus.SC_UNAUTHORIZED); + return; + } + + JITData JITInfo = (JITData) session.getAttribute(HandlerConstants.SESSION_JIT_DATA_KEY); + if (JITInfo == null) { + response.sendError(HttpStatus.SC_UNAUTHORIZED); + return; + } + + response.sendRedirect(JITInfo.getRedirectUrl() + "?code=" + request.getParameter("code") + + "&redirectUrl=" + JITProvisionCallbackURL); + } catch (IOException ex) { + log.error("Error occurred while processing JIT provisioning callback request", ex); + } + } +} diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java new file mode 100644 index 0000000000..61f6614cc9 --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.entgra.device.mgt.core.ui.request.interceptor; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.entgra.device.mgt.core.ui.request.interceptor.beans.JITData; +import io.entgra.device.mgt.core.ui.request.interceptor.beans.ProxyResponse; +import io.entgra.device.mgt.core.ui.request.interceptor.exceptions.JITEnrollmentException; +import io.entgra.device.mgt.core.ui.request.interceptor.exceptions.JITProvisionException; +import io.entgra.device.mgt.core.ui.request.interceptor.util.HandlerConstants; +import io.entgra.device.mgt.core.ui.request.interceptor.util.HandlerUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.wso2.carbon.utils.CarbonUtils; +import org.xml.sax.SAXException; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.File; +import java.io.IOException; +import java.util.Base64; +import java.util.Objects; + + +@WebServlet( + name = "JITProvisionRequestHandlerServlet", + description = "Handle Just In Time Provisioning requests", + urlPatterns = { + "/jit-provision" + } +) +public class JITProvisionHandler extends HttpServlet { + private static final Log log = LogFactory.getLog(JITProvisionHandler.class); + private String tenantDomain; + private String adminUsername; + private String clientId; + private String JITServiceProviderName; + private String apiManagerUrl; + private String encodedAdminCredentials; + private String encodedClientCredentials; + private String JITConfigurationPath; + private String JITCallbackUrl; + private String redirectUrl; + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) { + String keyManagerUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + + System.getProperty(HandlerConstants.IOT_KM_HOST_ENV_VAR) + + HandlerConstants.COLON + HandlerUtil.getKeyManagerPort(request.getScheme()); + JITCallbackUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + + System.getProperty(HandlerConstants.IOT_CORE_HOST_ENV_VAR) + + HandlerConstants.COLON + HandlerUtil.getCorePort(request.getScheme()) + + request.getContextPath() + + HandlerConstants.JIT_PROVISION_CALLBACK_URL; + apiManagerUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + + System.getProperty(HandlerConstants.IOT_APIM_HOST_ENV_VAR) + + HandlerConstants.COLON + HandlerUtil.getAPIManagerPort(request.getScheme()); + JITConfigurationPath = CarbonUtils.getCarbonConfigDirPath() + File.separator + "jit-config.xml"; + String scope = "openid"; + tenantDomain = request.getParameter("tenantDomain"); + redirectUrl = request.getParameter("redirectUrl"); + JITServiceProviderName = request.getParameter("sp"); + try { + if (tenantDomain == null || JITServiceProviderName == null) { + HandlerUtil.handleError(response, HttpStatus.SC_BAD_REQUEST); + return; + } + if (!initializeJITConfigurations()) { + HandlerUtil.handleError(response, HttpStatus.SC_SERVICE_UNAVAILABLE); + return; + } + + populateServiceProvider(); + persistJITData(request.getSession(true)); + response.sendRedirect(keyManagerUrl + HandlerConstants.AUTHORIZATION_ENDPOINT + + "?response_type=code" + + "&client_id=" + clientId + + "&state=" + + "&scope=" + scope + + "&redirect_uri=" + JITCallbackUrl); + } catch (JITProvisionException | IOException ex) { + log.error("Error occurred while processing JIT provisioning request", ex); + } + } + + /*** + * Construct dynamic client registration request + * @return {@link HttpPost} DCR request + */ + private HttpPost buildDCRRequest() { + HttpPost DCRRequest = new HttpPost(apiManagerUrl + HandlerConstants.DCR_URL); + DCRRequest.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); + DCRRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + encodedAdminCredentials); + JsonObject payload = new JsonObject(); + payload.addProperty("clientName", JITServiceProviderName); + payload.addProperty("owner", adminUsername); + payload.addProperty("saasApp", true); + payload.addProperty("grantType", HandlerConstants.CODE_GRANT_TYPE); + payload.addProperty("callbackUrl", JITCallbackUrl); + DCRRequest.setEntity(new StringEntity(payload.toString(), ContentType.APPLICATION_JSON)); + return DCRRequest; + } + + /*** + * Retrieve JIT data from current session if session exists, otherwise build and return + * @param session - {@link HttpSession} + * @return {@link JITData} + */ + private JITData getJITData(HttpSession session) { + return (session.getAttribute(HandlerConstants.SESSION_JIT_DATA_KEY) != null) ? + (JITData) session.getAttribute(HandlerConstants.SESSION_JIT_DATA_KEY) : new JITData(); + } + + /*** + * Persists JIT data in session + * @param session {@link HttpSession} + */ + private void persistJITData(HttpSession session) { + JITData JITInfo = getJITData(session); + JITInfo.setEncodedClientCredentials(encodedClientCredentials); + JITInfo.setTenantDomain(tenantDomain); + JITInfo.setRedirectUrl(redirectUrl); + JITInfo.setSp(JITServiceProviderName); + session.setMaxInactiveInterval(3600); + session.setAttribute(HandlerConstants.SESSION_JIT_DATA_KEY, JITInfo); + } + + /*** + * Populate service provider details + * @throws JITProvisionException throws when dcr request fails due to IO exception + */ + private void populateServiceProvider() throws JITProvisionException { + try { + HttpPost DCRRequest = buildDCRRequest(); + ProxyResponse proxyResponse = HandlerUtil.execute(DCRRequest); + if (proxyResponse.getCode() == HttpStatus.SC_OK) { + JsonObject serviceProvider = parseResponseData(proxyResponse.getData()); + clientId = serviceProvider.get("clientId").getAsString(); + String clientSecret = serviceProvider.get("clientSecret").getAsString(); + String headerValue = clientId + ':' + clientSecret; + encodedClientCredentials = Base64.getEncoder().encodeToString(headerValue.getBytes()); + } + } catch (IOException ex) { + String msg = "Error exception occurred while executing proxy request"; + throw new JITProvisionException(msg, ex); + } + } + + /*** + * Parse string data and build json object + * @param data - Json string + * @return {@link JsonObject} Json object corresponding to provided json string + * @throws JITProvisionException throws when error occurred while parsing + */ + private JsonObject parseResponseData(String data) throws JITProvisionException { + JsonParser parser = new JsonParser(); + JsonElement responseData = parser.parse(data); + if (responseData.isJsonObject()) { + return responseData.getAsJsonObject(); + } + throw new JITProvisionException("Unexpected response body return"); + } + + /*** + * Find the tenant based configurations and return + * @param tenantDomain - Domain of the tenant + * @param document - Config doc + * @return {@link Element} If config found return configuration element, otherwise null + */ + private Element findTenantConfigs(String tenantDomain, Document document) { + NodeList tenantConfigurations = document.getElementsByTagName("TenantConfiguration"); + for (int idx = 0; idx < tenantConfigurations.getLength(); idx++) { + Node configNode = tenantConfigurations.item(idx); + if (configNode.getNodeType() == Node.ELEMENT_NODE) { + Element configElement = (Element) configNode; + if (Objects.equals(configElement.getAttributes(). + getNamedItem("tenantDomain").getNodeValue(), tenantDomain)) { + return configElement; + } + } + } + return null; + } + + /*** + * Initialize JIT configurations + * @return boolean true when successful initialization, otherwise false + * @throws JITProvisionException throws when error occurred + */ + private boolean initializeJITConfigurations() throws JITProvisionException { + try { + File JITConfigurationFile = new File(JITConfigurationPath); + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document JITConfigurationDoc = documentBuilder.parse(JITConfigurationFile); + JITConfigurationDoc.getDocumentElement().normalize(); + Element tenantConfig = findTenantConfigs(tenantDomain, JITConfigurationDoc); + if (tenantConfig == null) return false; + adminUsername = tenantConfig.getElementsByTagName("AdminUsername").item(0).getTextContent(); + String adminPassword = tenantConfig.getElementsByTagName("AdminPassword").item(0).getTextContent(); + String headerValue = adminUsername + ":" + adminPassword; + encodedAdminCredentials = Base64.getEncoder().encodeToString(headerValue.getBytes()); + return true; + } catch (ParserConfigurationException ex) { + String msg = "Error occurred when document builder creating the file configuration"; + throw new JITProvisionException(msg, ex); + } catch (IOException ex) { + String msg = "IO error occurred while parsing the JIT config file"; + throw new JITProvisionException(msg, ex); + } catch (SAXException ex) { + String msg = "Parse error occurred while parsing the JIT config document"; + throw new JITProvisionException(msg, ex); + } + } +} diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/beans/JITData.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/beans/JITData.java new file mode 100644 index 0000000000..0e5f1769e7 --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/beans/JITData.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2018 - 2023 Entgra (Pvt) Ltd, Inc - All Rights Reserved. + * + * Unauthorised copying/redistribution of this file, via any medium is strictly prohibited. + * + * Licensed under the Entgra Commercial License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://entgra.io/licenses/entgra-commercial/1.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 io.entgra.device.mgt.core.ui.request.interceptor.beans; + +public class JITData { + private String username; + private String tenantDomain; + private String redirectUrl; + private String sp; + private String encodedClientCredentials; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getTenantDomain() { + return tenantDomain; + } + + public void setTenantDomain(String tenantDomain) { + this.tenantDomain = tenantDomain; + } + + public String getRedirectUrl() { + return redirectUrl; + } + + public void setRedirectUrl(String redirectUrl) { + this.redirectUrl = redirectUrl; + } + + public String getSp() { + return sp; + } + + public void setSp(String sp) { + this.sp = sp; + } + + public String getEncodedClientCredentials() { + return encodedClientCredentials; + } + + public void setEncodedClientCredentials(String encodedClientCredentials) { + this.encodedClientCredentials = encodedClientCredentials; + } +} diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/beans/JITEnrollmentData.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/beans/JITEnrollmentData.java new file mode 100644 index 0000000000..225d31cc91 --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/beans/JITEnrollmentData.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2018 - 2023 Entgra (Pvt) Ltd, Inc - All Rights Reserved. + * + * Unauthorised copying/redistribution of this file, via any medium is strictly prohibited. + * + * Licensed under the Entgra Commercial License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://entgra.io/licenses/entgra-commercial/1.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 io.entgra.device.mgt.core.ui.request.interceptor.beans; + +public class JITEnrollmentData { + private String username; + private String tenantDomain; + private String ownershipType; + private String os; + private String redirectUrl; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getTenantDomain() { + return tenantDomain; + } + + public void setTenantDomain(String tenantDomain) { + this.tenantDomain = tenantDomain; + } + + public String getOwnershipType() { + return ownershipType; + } + + public void setOwnershipType(String ownershipType) { + this.ownershipType = ownershipType; + } + + public String getOs() { + return os; + } + + public void setOs(String os) { + this.os = os; + } + + public String getRedirectUrl() { + return redirectUrl; + } + + public void setRedirectUrl(String redirectUrl) { + this.redirectUrl = redirectUrl; + } +} diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/exceptions/JITEnrollmentException.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/exceptions/JITEnrollmentException.java new file mode 100644 index 0000000000..c845f8b5c8 --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/exceptions/JITEnrollmentException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 - 2023 Entgra (Pvt) Ltd, Inc - All Rights Reserved. + * + * Unauthorised copying/redistribution of this file, via any medium is strictly prohibited. + * + * Licensed under the Entgra Commercial License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://entgra.io/licenses/entgra-commercial/1.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 io.entgra.device.mgt.core.ui.request.interceptor.exceptions; + +public class JITEnrollmentException extends Exception { + public JITEnrollmentException(String msg, Throwable t) { + super(msg, t); + } + public JITEnrollmentException(String msg) { + super(msg); + } +} + diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/exceptions/JITProvisionException.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/exceptions/JITProvisionException.java new file mode 100644 index 0000000000..fc3018890d --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/exceptions/JITProvisionException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 - 2023 Entgra (Pvt) Ltd, Inc - All Rights Reserved. + * + * Unauthorised copying/redistribution of this file, via any medium is strictly prohibited. + * + * Licensed under the Entgra Commercial License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://entgra.io/licenses/entgra-commercial/1.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 io.entgra.device.mgt.core.ui.request.interceptor.exceptions; + +public class JITProvisionException extends Exception { + public JITProvisionException(String msg, Throwable t) { + super(msg, t); + } + public JITProvisionException(String msg) { + super(msg); + } +} diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/util/HandlerConstants.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/util/HandlerConstants.java index 9aa9cb9fe6..f7f4399a77 100644 --- a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/util/HandlerConstants.java +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/util/HandlerConstants.java @@ -107,4 +107,12 @@ public class HandlerConstants { public static final String USER_SCOPES = "userScopes"; public static final String HUBSPOT_CHAT_URL = "api.hubapi.com"; public static final String USERNAME_WITH_DOMAIN = "usernameWithDomain"; + public static final String JIT_PROVISION_CALLBACK_URL = "/jit-provision-callback"; + public static final String JIT_ENROLLMENT_HANDLER_CALLBACK_URL = "/jit-enrollment-callback"; + public static final String DCR_URL = "/client-registration/v0.17/register"; + public static final String SESSION_JIT_DATA_KEY = "JITInfo"; + public static final String SESSION_JIT_ENROLLMENT_DATA_KEY = "JITEnrollmentInfo"; + public static final String JIT_PROVISION_HANDLER = "/jit-provision"; + public static final String JIT_ENROLLMENT_AUTH_APP_KEY = "JIT_ENROLLMENT_AUTH_APP"; + public static final String CLIENT_CREDENTIAL_GRANT_TYPE = "client_credentials"; } diff --git a/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/jit-config.xml b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/jit-config.xml new file mode 100644 index 0000000000..ae22e570ab --- /dev/null +++ b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/jit-config.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/email/templates/default-jit-enrollment-invitation.vm b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/email/templates/default-jit-enrollment-invitation.vm new file mode 100644 index 0000000000..4487662397 --- /dev/null +++ b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/email/templates/default-jit-enrollment-invitation.vm @@ -0,0 +1,61 @@ +#* + Copyright (C) 2018 - 2023 Entgra (Pvt) Ltd, Inc - All Rights Reserved. + + Unauthorised copying/redistribution of this file, via any medium is strictly prohibited. + + Licensed under the Entgra Commercial License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://entgra.io/licenses/entgra-commercial/1.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. +*# + + You have been invited to enroll your $device-type device in Entgra IoT + + + + Entgra IoT Server + + +
+
+
+
+ entgra +
+
+
+

+ Hi $username, +

+

+ You have been invited by $invite-by to enrol your $device-type device in Entgra IoT Server. + Click here to begin device + enrolment.

+ +

+ Should you need assistance, please contact your administrator. +

+ +

+ Regards, +

+

+ Entgra IoT Administrator +

+
+
+
+ + + ]]> + +
\ No newline at end of file diff --git a/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/p2.inf b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/p2.inf index e861fe0e56..80897977ed 100644 --- a/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/p2.inf +++ b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/p2.inf @@ -13,3 +13,4 @@ org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../../r org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/io.entgra.device.mgt.core.device.mgt.basics_${feature.version}/email/templates,target:${installFolder}/../../../repository/resources/email-templates,overwrite:true);\ org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/io.entgra.device.mgt.core.device.mgt.basics_${feature.version}/conf_templates/,target:${installFolder}/../../resources/conf/,overwrite:true);\ org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/io.entgra.device.mgt.core.device.mgt.basics_${feature.version}/conf/traccar-config.xml,target:${installFolder}/../../conf/traccar-config.xml,overwrite:true);\ +org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/io.entgra.device.mgt.core.device.mgt.basics_${feature.version}/conf/jit-config.xml,target:${installFolder}/../../conf/jit-config.xml,overwrite:true);\ diff --git a/features/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor.feature/src/main/resources/p2.inf b/features/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor.feature/src/main/resources/p2.inf index df48e94eb2..1e33a85fad 100644 --- a/features/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor.feature/src/main/resources/p2.inf +++ b/features/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor.feature/src/main/resources/p2.inf @@ -4,4 +4,5 @@ org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../featur org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/io.entgra.device.mgt.core.ui.request.interceptor_${feature.version}/webapps/ui-request-handler.war,target:${installFolder}/../../deployment/server/webapps/store-ui-request-handler.war,overwrite:true);\ org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/io.entgra.device.mgt.core.ui.request.interceptor_${feature.version}/webapps/ui-request-handler.war,target:${installFolder}/../../deployment/server/webapps/entgra-ui-request-handler.war,overwrite:true);\ org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/io.entgra.device.mgt.core.ui.request.interceptor_${feature.version}/webapps/ui-request-handler.war,target:${installFolder}/../../deployment/server/webapps/mdm-reports-ui-request-handler.war,overwrite:true);\ +org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/io.entgra.device.mgt.core.ui.request.interceptor_${feature.version}/webapps/ui-request-handler.war,target:${installFolder}/../../deployment/server/webapps/enroll-web-agent-ui-request-handler.war,overwrite:true);\ org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/io.entgra.device.mgt.core.ui.request.interceptor_${feature.version}/payloads/,target:${installFolder}/../../resources/payloads/,overwrite:true);\ From 76064844c4c1820b2d2d604c2a06faede87228ae Mon Sep 17 00:00:00 2001 From: rajitha Date: Tue, 26 Sep 2023 12:56:06 +0530 Subject: [PATCH 2/5] Add generic logic to jit handlers --- .../JITEnrollmentCallbackHandler.java | 66 +++++++++++++- .../interceptor/JITEnrollmentHandler.java | 5 +- .../interceptor/JITProvisionHandler.java | 88 +++---------------- .../src/main/resources/conf/jit-config.xml | 32 +++++-- 4 files changed, 104 insertions(+), 87 deletions(-) diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentCallbackHandler.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentCallbackHandler.java index 3d314bf79d..e0b33691b1 100644 --- a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentCallbackHandler.java +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentCallbackHandler.java @@ -36,14 +36,25 @@ import org.apache.http.HttpHeaders; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.wso2.carbon.utils.CarbonUtils; +import org.xml.sax.SAXException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.File; import java.io.IOException; import java.util.Base64; +import java.util.Objects; @WebServlet( name = "JIT Enrollment callback handler", @@ -62,7 +73,8 @@ public class JITEnrollmentCallbackHandler extends HttpServlet { private String clientId; private String clientSecret; private String scope; - + private String JITConfigurationPath; + private JITEnrollmentData JITEnrollmentInfo; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) { gatewayUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR @@ -71,6 +83,7 @@ public class JITEnrollmentCallbackHandler extends HttpServlet { keyManagerUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_KM_HOST_ENV_VAR) + HandlerConstants.COLON + HandlerUtil.getKeyManagerPort(request.getScheme()); + JITConfigurationPath = CarbonUtils.getCarbonConfigDirPath() + File.separator + "jit-config.xml"; HttpSession session = request.getSession(false); try { if (session == null) { @@ -84,7 +97,7 @@ public class JITEnrollmentCallbackHandler extends HttpServlet { return; } - JITEnrollmentData JITEnrollmentInfo = (JITEnrollmentData) + JITEnrollmentInfo = (JITEnrollmentData) session.getAttribute(HandlerConstants.SESSION_JIT_ENROLLMENT_DATA_KEY); if (JITEnrollmentInfo == null) { response.sendError(HttpStatus.SC_UNAUTHORIZED); @@ -92,8 +105,7 @@ public class JITEnrollmentCallbackHandler extends HttpServlet { } applicationName = request.getContextPath().substring(1, request.getContextPath().indexOf("-ui-request-handler")); - scope = "perm:metadata:view perm:metadata:create perm:metadata:update perm:android:enroll " + - "perm:device:enroll perm:android:view-configuration"; + initializeJITEnrollmentConfigurations(); populateApplicationData(registerApplication()); persistAuthData(session, getToken()); response.sendRedirect(JITEnrollmentInfo.getRedirectUrl() + "?ownershipType=" + @@ -104,6 +116,52 @@ public class JITEnrollmentCallbackHandler extends HttpServlet { } } + private void initializeJITEnrollmentConfigurations() throws JITEnrollmentException { + try { + File JITConfigurationFile = new File(JITConfigurationPath); + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document JITConfigurationDoc = documentBuilder.parse(JITConfigurationFile); + JITConfigurationDoc.getDocumentElement().normalize(); + Element enrollmentScopes; + if (Objects.equals(JITEnrollmentInfo.getOs(), "android")) { + enrollmentScopes = (Element) JITConfigurationDoc. + getElementsByTagName("AndroidEnrollmentScopes").item(0); + } else if (Objects.equals(JITEnrollmentInfo.getOs(), "ios")) { + enrollmentScopes = (Element) JITConfigurationDoc. + getElementsByTagName("IOSEnrollmentScopes").item(0); + } else if (Objects.equals(JITEnrollmentInfo.getOs(), "windows")) { + enrollmentScopes = (Element) JITConfigurationDoc. + getElementsByTagName("IOSEnrollmentScopes").item(0); + } else { + String msg = "OS type not supported"; + if (log.isDebugEnabled()) { + log.error(msg); + } + throw new JITEnrollmentException(msg); + } + NodeList scopeList = enrollmentScopes.getElementsByTagName("Scope"); + StringBuilder scopeStr = new StringBuilder(); + for (int idx = 0; idx < scopeList.getLength(); idx++) { + Node scopeNode = scopeList.item(idx); + if (scopeNode.getNodeType() == Node.ELEMENT_NODE) { + Element scopeElement = (Element) scopeNode; + scopeStr.append(" ").append(scopeElement.getTextContent()); + } + } + scope = scopeStr.toString(); + } catch (ParserConfigurationException ex) { + String msg = "Error occurred when document builder creating the file configuration"; + throw new JITEnrollmentException(msg, ex); + } catch (IOException ex) { + String msg = "IO error occurred while parsing the JIT config file"; + throw new JITEnrollmentException(msg, ex); + } catch (SAXException ex) { + String msg = "Parse error occurred while parsing the JIT config document"; + throw new JITEnrollmentException(msg, ex); + } + } + /*** * Parse string data and build json object * @param data - Json string diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentHandler.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentHandler.java index b13d4d904a..8ca9cdf98c 100644 --- a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentHandler.java +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentHandler.java @@ -19,10 +19,14 @@ package io.entgra.device.mgt.core.ui.request.interceptor; import io.entgra.device.mgt.core.ui.request.interceptor.beans.JITEnrollmentData; +import io.entgra.device.mgt.core.ui.request.interceptor.exceptions.JITEnrollmentException; import io.entgra.device.mgt.core.ui.request.interceptor.util.HandlerConstants; import io.entgra.device.mgt.core.ui.request.interceptor.util.HandlerUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Document; +import org.wso2.carbon.utils.CarbonUtils; +import org.xml.sax.SAXException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; @@ -45,7 +49,6 @@ public class JITEnrollmentHandler extends HttpServlet { private String os; private String redirectUrl; private String tenantDomain; - @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java index 61f6614cc9..dec743289e 100644 --- a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java @@ -65,14 +65,10 @@ import java.util.Objects; public class JITProvisionHandler extends HttpServlet { private static final Log log = LogFactory.getLog(JITProvisionHandler.class); private String tenantDomain; - private String adminUsername; private String clientId; private String JITServiceProviderName; - private String apiManagerUrl; - private String encodedAdminCredentials; private String encodedClientCredentials; private String JITConfigurationPath; - private String JITCallbackUrl; private String redirectUrl; @Override @@ -80,14 +76,11 @@ public class JITProvisionHandler extends HttpServlet { String keyManagerUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_KM_HOST_ENV_VAR) + HandlerConstants.COLON + HandlerUtil.getKeyManagerPort(request.getScheme()); - JITCallbackUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + String JITCallbackUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_CORE_HOST_ENV_VAR) + HandlerConstants.COLON + HandlerUtil.getCorePort(request.getScheme()) + request.getContextPath() + HandlerConstants.JIT_PROVISION_CALLBACK_URL; - apiManagerUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR - + System.getProperty(HandlerConstants.IOT_APIM_HOST_ENV_VAR) - + HandlerConstants.COLON + HandlerUtil.getAPIManagerPort(request.getScheme()); JITConfigurationPath = CarbonUtils.getCarbonConfigDirPath() + File.separator + "jit-config.xml"; String scope = "openid"; tenantDomain = request.getParameter("tenantDomain"); @@ -103,7 +96,6 @@ public class JITProvisionHandler extends HttpServlet { return; } - populateServiceProvider(); persistJITData(request.getSession(true)); response.sendRedirect(keyManagerUrl + HandlerConstants.AUTHORIZATION_ENDPOINT + "?response_type=code" + @@ -116,24 +108,6 @@ public class JITProvisionHandler extends HttpServlet { } } - /*** - * Construct dynamic client registration request - * @return {@link HttpPost} DCR request - */ - private HttpPost buildDCRRequest() { - HttpPost DCRRequest = new HttpPost(apiManagerUrl + HandlerConstants.DCR_URL); - DCRRequest.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); - DCRRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + encodedAdminCredentials); - JsonObject payload = new JsonObject(); - payload.addProperty("clientName", JITServiceProviderName); - payload.addProperty("owner", adminUsername); - payload.addProperty("saasApp", true); - payload.addProperty("grantType", HandlerConstants.CODE_GRANT_TYPE); - payload.addProperty("callbackUrl", JITCallbackUrl); - DCRRequest.setEntity(new StringEntity(payload.toString(), ContentType.APPLICATION_JSON)); - return DCRRequest; - } - /*** * Retrieve JIT data from current session if session exists, otherwise build and return * @param session - {@link HttpSession} @@ -158,56 +132,22 @@ public class JITProvisionHandler extends HttpServlet { session.setAttribute(HandlerConstants.SESSION_JIT_DATA_KEY, JITInfo); } - /*** - * Populate service provider details - * @throws JITProvisionException throws when dcr request fails due to IO exception - */ - private void populateServiceProvider() throws JITProvisionException { - try { - HttpPost DCRRequest = buildDCRRequest(); - ProxyResponse proxyResponse = HandlerUtil.execute(DCRRequest); - if (proxyResponse.getCode() == HttpStatus.SC_OK) { - JsonObject serviceProvider = parseResponseData(proxyResponse.getData()); - clientId = serviceProvider.get("clientId").getAsString(); - String clientSecret = serviceProvider.get("clientSecret").getAsString(); - String headerValue = clientId + ':' + clientSecret; - encodedClientCredentials = Base64.getEncoder().encodeToString(headerValue.getBytes()); - } - } catch (IOException ex) { - String msg = "Error exception occurred while executing proxy request"; - throw new JITProvisionException(msg, ex); - } - } - - /*** - * Parse string data and build json object - * @param data - Json string - * @return {@link JsonObject} Json object corresponding to provided json string - * @throws JITProvisionException throws when error occurred while parsing - */ - private JsonObject parseResponseData(String data) throws JITProvisionException { - JsonParser parser = new JsonParser(); - JsonElement responseData = parser.parse(data); - if (responseData.isJsonObject()) { - return responseData.getAsJsonObject(); - } - throw new JITProvisionException("Unexpected response body return"); - } - /*** * Find the tenant based configurations and return * @param tenantDomain - Domain of the tenant * @param document - Config doc * @return {@link Element} If config found return configuration element, otherwise null */ - private Element findTenantConfigs(String tenantDomain, Document document) { - NodeList tenantConfigurations = document.getElementsByTagName("TenantConfiguration"); - for (int idx = 0; idx < tenantConfigurations.getLength(); idx++) { - Node configNode = tenantConfigurations.item(idx); + private Element findServiceProvider(String tenantDomain, Document document) { + NodeList serviceProviderConfiguration = document.getElementsByTagName("ServiceProvider"); + for (int idx = 0; idx < serviceProviderConfiguration.getLength(); idx++) { + Node configNode = serviceProviderConfiguration.item(idx); if (configNode.getNodeType() == Node.ELEMENT_NODE) { Element configElement = (Element) configNode; if (Objects.equals(configElement.getAttributes(). - getNamedItem("tenantDomain").getNodeValue(), tenantDomain)) { + getNamedItem("tenantDomain").getNodeValue(), tenantDomain) && + Objects.equals(configElement.getAttributes().getNamedItem("name").getNodeValue(), + JITServiceProviderName)) { return configElement; } } @@ -227,12 +167,12 @@ public class JITProvisionHandler extends HttpServlet { DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document JITConfigurationDoc = documentBuilder.parse(JITConfigurationFile); JITConfigurationDoc.getDocumentElement().normalize(); - Element tenantConfig = findTenantConfigs(tenantDomain, JITConfigurationDoc); - if (tenantConfig == null) return false; - adminUsername = tenantConfig.getElementsByTagName("AdminUsername").item(0).getTextContent(); - String adminPassword = tenantConfig.getElementsByTagName("AdminPassword").item(0).getTextContent(); - String headerValue = adminUsername + ":" + adminPassword; - encodedAdminCredentials = Base64.getEncoder().encodeToString(headerValue.getBytes()); + Element serviceProvider = findServiceProvider(tenantDomain, JITConfigurationDoc); + if (serviceProvider == null) return false; + clientId = serviceProvider.getElementsByTagName("ClientId").item(0).getTextContent(); + String clientSecret = serviceProvider.getElementsByTagName("ClientSecret").item(0).getTextContent(); + String headerValue = clientId + ":" + clientSecret; + encodedClientCredentials = Base64.getEncoder().encodeToString(headerValue.getBytes()); return true; } catch (ParserConfigurationException ex) { String msg = "Error occurred when document builder creating the file configuration"; diff --git a/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/jit-config.xml b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/jit-config.xml index ae22e570ab..ef59cd01f0 100644 --- a/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/jit-config.xml +++ b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/jit-config.xml @@ -18,11 +18,27 @@ ~ under the License. --> - - - \ No newline at end of file + + + + perm:metadata:view + perm:metadata:create + perm:metadata:update + perm:android:enroll + perm:device:enroll + perm:android:view-configuration + + + + + + + + + + + + \ No newline at end of file From 93427e00778ff4bb6bbb84ac4d94720252fbb4bc Mon Sep 17 00:00:00 2001 From: Rajitha Kumara Date: Thu, 2 Nov 2023 08:40:17 +0530 Subject: [PATCH 3/5] Update scopes --- .../src/main/resources/conf/jit-config.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/jit-config.xml b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/jit-config.xml index ef59cd01f0..1b72287a08 100644 --- a/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/jit-config.xml +++ b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/jit-config.xml @@ -21,12 +21,12 @@ - perm:metadata:view - perm:metadata:create - perm:metadata:update - perm:android:enroll - perm:device:enroll - perm:android:view-configuration + dm:metadata:view + dm:metadata:create + dm:metadata:update + and:devices:enroll + dm:device:enroll + and:conf:view From a9aa66173ac0897a2598564c6894596656a04936 Mon Sep 17 00:00:00 2001 From: Rajitha Kumara Date: Thu, 2 Nov 2023 09:33:27 +0530 Subject: [PATCH 4/5] Add csrf protection for provision handlers --- .../request/interceptor/JITProvisionCallbackHandler.java | 8 ++++++++ .../core/ui/request/interceptor/JITProvisionHandler.java | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionCallbackHandler.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionCallbackHandler.java index 0214069df0..433c1e7de3 100644 --- a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionCallbackHandler.java +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionCallbackHandler.java @@ -32,6 +32,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; +import java.util.Objects; @WebServlet( name = "JIT callback handler", @@ -45,6 +46,7 @@ public class JITProvisionCallbackHandler extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) { + String state = request.getParameter("state"); HttpSession session = request.getSession(false); String JITProvisionCallbackURL = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_CORE_HOST_ENV_VAR) @@ -57,6 +59,12 @@ public class JITProvisionCallbackHandler extends HttpServlet { return; } + if (state == null || !Objects.equals(state, session.getAttribute("state").toString())) { + response.sendError(org.apache.http.HttpStatus.SC_BAD_REQUEST, "MismatchingStateError: CSRF Warning! " + + "State not equal in request and response"); + return; + } + JITData JITInfo = (JITData) session.getAttribute(HandlerConstants.SESSION_JIT_DATA_KEY); if (JITInfo == null) { response.sendError(HttpStatus.SC_UNAUTHORIZED); diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java index dec743289e..5ff97d4a87 100644 --- a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java @@ -70,6 +70,7 @@ public class JITProvisionHandler extends HttpServlet { private String encodedClientCredentials; private String JITConfigurationPath; private String redirectUrl; + private String state; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) { @@ -83,6 +84,7 @@ public class JITProvisionHandler extends HttpServlet { + HandlerConstants.JIT_PROVISION_CALLBACK_URL; JITConfigurationPath = CarbonUtils.getCarbonConfigDirPath() + File.separator + "jit-config.xml"; String scope = "openid"; + state = HandlerUtil.generateStateToken(); tenantDomain = request.getParameter("tenantDomain"); redirectUrl = request.getParameter("redirectUrl"); JITServiceProviderName = request.getParameter("sp"); @@ -100,7 +102,7 @@ public class JITProvisionHandler extends HttpServlet { response.sendRedirect(keyManagerUrl + HandlerConstants.AUTHORIZATION_ENDPOINT + "?response_type=code" + "&client_id=" + clientId + - "&state=" + + "&state=" + state + "&scope=" + scope + "&redirect_uri=" + JITCallbackUrl); } catch (JITProvisionException | IOException ex) { @@ -129,6 +131,7 @@ public class JITProvisionHandler extends HttpServlet { JITInfo.setRedirectUrl(redirectUrl); JITInfo.setSp(JITServiceProviderName); session.setMaxInactiveInterval(3600); + session.setAttribute("state", state); session.setAttribute(HandlerConstants.SESSION_JIT_DATA_KEY, JITInfo); } From b144be0f970e40953bb0c34c999f47b888569f20 Mon Sep 17 00:00:00 2001 From: Rajitha Kumara Date: Mon, 1 Jan 2024 13:59:25 +0530 Subject: [PATCH 5/5] Add requested changes --- .../impl/UserManagementServiceImpl.java | 18 +++++++++------- .../JITEnrollmentCallbackHandler.java | 12 +++++------ .../interceptor/JITProvisionHandler.java | 21 ++++++++++++------- .../interceptor/util/HandlerConstants.java | 6 ++++++ 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/UserManagementServiceImpl.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/UserManagementServiceImpl.java index 2ed0d17bee..bc3a26b88d 100644 --- a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/UserManagementServiceImpl.java +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.api/src/main/java/io/entgra/device/mgt/core/device/mgt/api/jaxrs/service/impl/UserManagementServiceImpl.java @@ -1228,15 +1228,18 @@ public class UserManagementServiceImpl implements UserManagementService { } private String getTemplateName(String deviceType, String prefix, String separator) throws NoSuchFileException { - String templateName = deviceType + separator + prefix; - File template = new File(CarbonUtils.getCarbonHome() + File.separator + "repository" + File.separator - + "resources" + File.separator + "email-templates" + File.separator + templateName + ".vm"); + String templateName = deviceType + separator + prefix + ".vm"; + List templatePathSegments = + Arrays.asList(CarbonUtils.getCarbonHome(), "repository", "resources", "email-templates", templateName); + File template = new File(String.join(File.separator, templatePathSegments)); if (template.exists()) { return templateName; } - String defaultTemplateName = "default" + separator + prefix; - File defaultTemplate = new File(CarbonUtils.getCarbonHome() + File.separator + "repository" + File.separator - + "resources" + File.separator + "email-templates" + File.separator + defaultTemplateName + ".vm"); + + String defaultTemplateName = "default" + separator + prefix + ".vm"; + List defaultTemplatePathSegments = + Arrays.asList(CarbonUtils.getCarbonHome(), "repository", "resources", "email-templates", defaultTemplateName); + File defaultTemplate = new File(String.join(File.separator, defaultTemplatePathSegments)); if (defaultTemplate.exists()) { if (log.isDebugEnabled()) { @@ -1245,8 +1248,7 @@ public class UserManagementServiceImpl implements UserManagementService { return defaultTemplateName; } - String msg = "Didn't found template file for " + templateName; - throw new NoSuchFileException(msg); + throw new NoSuchFileException("Didn't found template file for " + templateName); } /** diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentCallbackHandler.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentCallbackHandler.java index e0b33691b1..6a82ae3f26 100644 --- a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentCallbackHandler.java +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITEnrollmentCallbackHandler.java @@ -124,15 +124,15 @@ public class JITEnrollmentCallbackHandler extends HttpServlet { Document JITConfigurationDoc = documentBuilder.parse(JITConfigurationFile); JITConfigurationDoc.getDocumentElement().normalize(); Element enrollmentScopes; - if (Objects.equals(JITEnrollmentInfo.getOs(), "android")) { + if (Objects.equals(JITEnrollmentInfo.getOs(), HandlerConstants.OS_ANDROID)) { enrollmentScopes = (Element) JITConfigurationDoc. - getElementsByTagName("AndroidEnrollmentScopes").item(0); - } else if (Objects.equals(JITEnrollmentInfo.getOs(), "ios")) { + getElementsByTagName(HandlerConstants.TAG_ANDROID_ENROLLMENT_SCOPES).item(0); + } else if (Objects.equals(JITEnrollmentInfo.getOs(), HandlerConstants.OS_IOS)) { enrollmentScopes = (Element) JITConfigurationDoc. - getElementsByTagName("IOSEnrollmentScopes").item(0); - } else if (Objects.equals(JITEnrollmentInfo.getOs(), "windows")) { + getElementsByTagName(HandlerConstants.TAG_IOS_ENROLLMENT_SCOPES).item(0); + } else if (Objects.equals(JITEnrollmentInfo.getOs(), HandlerConstants.OS_WINDOWS)) { enrollmentScopes = (Element) JITConfigurationDoc. - getElementsByTagName("IOSEnrollmentScopes").item(0); + getElementsByTagName(HandlerConstants.TAG_WINDOWS_ENROLLMENT_SCOPES).item(0); } else { String msg = "OS type not supported"; if (log.isDebugEnabled()) { diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java index 5ff97d4a87..9c1cf31a94 100644 --- a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/JITProvisionHandler.java @@ -52,6 +52,8 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.File; import java.io.IOException; import java.util.Base64; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; @@ -71,6 +73,7 @@ public class JITProvisionHandler extends HttpServlet { private String JITConfigurationPath; private String redirectUrl; private String state; + private static final Map tenantConfigs = new HashMap<>(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) { @@ -165,13 +168,17 @@ public class JITProvisionHandler extends HttpServlet { */ private boolean initializeJITConfigurations() throws JITProvisionException { try { - File JITConfigurationFile = new File(JITConfigurationPath); - DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); - Document JITConfigurationDoc = documentBuilder.parse(JITConfigurationFile); - JITConfigurationDoc.getDocumentElement().normalize(); - Element serviceProvider = findServiceProvider(tenantDomain, JITConfigurationDoc); - if (serviceProvider == null) return false; + Element serviceProvider = tenantConfigs.get(tenantDomain); + if (serviceProvider == null) { + File JITConfigurationFile = new File(JITConfigurationPath); + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document JITConfigurationDoc = documentBuilder.parse(JITConfigurationFile); + JITConfigurationDoc.getDocumentElement().normalize(); + serviceProvider = findServiceProvider(tenantDomain, JITConfigurationDoc); + if (serviceProvider == null) return false; + tenantConfigs.put(tenantDomain, serviceProvider); + } clientId = serviceProvider.getElementsByTagName("ClientId").item(0).getTextContent(); String clientSecret = serviceProvider.getElementsByTagName("ClientSecret").item(0).getTextContent(); String headerValue = clientId + ":" + clientSecret; diff --git a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/util/HandlerConstants.java b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/util/HandlerConstants.java index f7f4399a77..44aedaafe5 100644 --- a/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/util/HandlerConstants.java +++ b/components/ui-request-interceptor/io.entgra.device.mgt.core.ui.request.interceptor/src/main/java/io/entgra/device/mgt/core/ui/request/interceptor/util/HandlerConstants.java @@ -115,4 +115,10 @@ public class HandlerConstants { public static final String JIT_PROVISION_HANDLER = "/jit-provision"; public static final String JIT_ENROLLMENT_AUTH_APP_KEY = "JIT_ENROLLMENT_AUTH_APP"; public static final String CLIENT_CREDENTIAL_GRANT_TYPE = "client_credentials"; + public static final String OS_ANDROID = "android"; + public static final String OS_WINDOWS = "windows"; + public static final String OS_IOS = "ios"; + public static final String TAG_ANDROID_ENROLLMENT_SCOPES = "AndroidEnrollmentScopes"; + public static final String TAG_WINDOWS_ENROLLMENT_SCOPES = "WindowsEnrollmentScopes"; + public static final String TAG_IOS_ENROLLMENT_SCOPES = "IOSEnrollmentScopes"; }