diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/ui/ExtraQueryParam.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/ui/ExtraQueryParam.java new file mode 100644 index 0000000000..5c7a6cd6c1 --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/ui/ExtraQueryParam.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 - 2024, 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.device.mgt.core.config.ui; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "ExtraQueryParam") +public class ExtraQueryParam { + private String queryParam; + private String paramValue; + + @XmlElement(name = "QueryParam") + public String getQueryParam() { + return queryParam; + } + + public void setQueryParam(String queryParam) { + this.queryParam = queryParam; + } + + @XmlElement(name = "ParamValue") + public String getParamValue() { + return paramValue; + } + + public void setParamValue(String paramValue) { + this.paramValue = paramValue; + } +} diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/ui/SSOConfiguration.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/ui/SSOConfiguration.java new file mode 100644 index 0000000000..d1016c2798 --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/ui/SSOConfiguration.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 - 2024, 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.device.mgt.core.config.ui; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.List; + +@XmlRootElement(name = "SSOConfiguration") +public class SSOConfiguration { + private List authEndpointExtraQueryParam; + + @XmlElementWrapper(name = "AuthEndpointExtraQueryParams") + @XmlElement(name = "ExtraQueryParam") + public List getAuthEndpointExtraQueryParam() { + return authEndpointExtraQueryParam; + } + + public void setAuthEndpointExtraQueryParam(List authEndpointExtraQueryParam) { + this.authEndpointExtraQueryParam = authEndpointExtraQueryParam; + } +} diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/ui/UIConfiguration.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/ui/UIConfiguration.java index 4dd9cbfe4a..180d19138e 100644 --- a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/ui/UIConfiguration.java +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/ui/UIConfiguration.java @@ -35,6 +35,7 @@ public class UIConfiguration { private int loginCacheCapacity; private Billing billing; private HubspotChat hubspotChat; + private SSOConfiguration ssoConfiguration; private DeviceInfoConfigurations deviceInfoConfigurations; @@ -121,4 +122,13 @@ public class UIConfiguration { public void setDeviceStatusConfigurations(DeviceStatusConfigurations deviceStatusConfigurations) { this.deviceStatusConfigurations = deviceStatusConfigurations; } + + @XmlElement(name = "SSOConfiguration", required = true) + public SSOConfiguration getSsoConfiguration() { + return ssoConfiguration; + } + + public void setSsoConfiguration(SSOConfiguration ssoConfiguration) { + this.ssoConfiguration = ssoConfiguration; + } } 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 6a82ae3f26..00b63e6abb 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 @@ -18,7 +18,6 @@ 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; @@ -53,7 +52,6 @@ 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( @@ -65,25 +63,26 @@ import java.util.Objects; ) public class JITEnrollmentCallbackHandler extends HttpServlet { private static final Log log = LogFactory.getLog(JITEnrollmentCallbackHandler.class); - private String gatewayUrl; private String keyManagerUrl; + private String JITProvisionCallbackUrl; private JITData JITInfo; - private String encodedClientCredentials; - private String applicationName; - private String clientId; - private String clientSecret; private String scope; private String JITConfigurationPath; private JITEnrollmentData JITEnrollmentInfo; + private String code; + @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()); + 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; JITConfigurationPath = CarbonUtils.getCarbonConfigDirPath() + File.separator + "jit-config.xml"; + HttpSession session = request.getSession(false); try { if (session == null) { @@ -91,6 +90,14 @@ public class JITEnrollmentCallbackHandler extends HttpServlet { return; } + String state = request.getParameter("state"); + 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; + } + + code = request.getParameter("code"); JITInfo = (JITData) session.getAttribute(HandlerConstants.SESSION_JIT_DATA_KEY); if (JITInfo == null) { response.sendError(HttpStatus.SC_UNAUTHORIZED); @@ -103,10 +110,8 @@ public class JITEnrollmentCallbackHandler extends HttpServlet { response.sendError(HttpStatus.SC_UNAUTHORIZED); return; } - applicationName = request.getContextPath().substring(1, - request.getContextPath().indexOf("-ui-request-handler")); + initializeJITEnrollmentConfigurations(); - populateApplicationData(registerApplication()); persistAuthData(session, getToken()); response.sendRedirect(JITEnrollmentInfo.getRedirectUrl() + "?ownershipType=" + JITEnrollmentInfo.getOwnershipType() + "&os=" + JITEnrollmentInfo.getOs() + "&username=" + @@ -177,55 +182,6 @@ public class JITEnrollmentCallbackHandler extends HttpServlet { 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 @@ -250,11 +206,11 @@ public class JITEnrollmentCallbackHandler extends HttpServlet { */ private HttpPost buildTokenAcquireRequest() { HttpPost tokenAcquiringRequest = new HttpPost(keyManagerUrl + HandlerConstants.OAUTH2_TOKEN_ENDPOINT); + tokenAcquiringRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + JITInfo.getEncodedClientCredentials()); 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, + "grant_type=" + HandlerConstants.CODE_GRANT_TYPE + "&code=" + code + "&scope=" + scope + + "&redirect_uri=" + JITProvisionCallbackUrl, ContentType.APPLICATION_FORM_URLENCODED); tokenAcquiringRequest.setEntity(payload); return tokenAcquiringRequest; @@ -268,9 +224,10 @@ public class JITEnrollmentCallbackHandler extends HttpServlet { 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.setRefreshToken(token.get("refresh_token").getAsString()); + authData.setClientId(JITInfo.getClientId()); + authData.setClientSecret(JITInfo.getClientSecret()); + authData.setEncodedClientApp(JITInfo.getEncodedClientCredentials()); 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 index 8ca9cdf98c..8bf15198d7 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 @@ -49,6 +49,8 @@ public class JITEnrollmentHandler extends HttpServlet { private String os; private String redirectUrl; private String tenantDomain; + private String state; + @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { @@ -58,11 +60,12 @@ public class JITEnrollmentHandler extends HttpServlet { + HandlerConstants.COLON + HandlerUtil.getCorePort(request.getScheme()) + request.getContextPath() + HandlerConstants.JIT_PROVISION_HANDLER; - String onCompletionUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + String JITEnrollmentCallbackUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_CORE_HOST_ENV_VAR) + HandlerConstants.COLON + HandlerUtil.getCorePort(request.getScheme()) + request.getContextPath() + "/jit-enrollment-callback"; + state = HandlerUtil.generateStateToken(); username = request.getParameter("username"); ownershipType = request.getParameter("ownershipType"); os = request.getParameter("os"); @@ -71,7 +74,7 @@ public class JITEnrollmentHandler extends HttpServlet { String sp = request.getParameter("sp"); persistJITData(session); response.sendRedirect(JITProvisionHandlerUrl + "?tenantDomain=" + tenantDomain - + "&sp=" + sp + "&redirectUrl=" + onCompletionUrl); + + "&sp=" + sp + "&redirectUrl=" + JITEnrollmentCallbackUrl); } catch (IOException ex) { log.error("Error occurred while handling JIT enrollment request"); } @@ -88,6 +91,7 @@ public class JITEnrollmentHandler extends HttpServlet { JITEnrollmentInfo.setUsername(username); JITEnrollmentInfo.setRedirectUrl(redirectUrl); JITEnrollmentInfo.setTenantDomain(tenantDomain); + session.setAttribute("state", state); 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 index 433c1e7de3..b9a537fca2 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 @@ -18,13 +18,22 @@ 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.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.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.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; @@ -32,6 +41,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; +import java.util.Base64; import java.util.Objects; @WebServlet( @@ -43,38 +53,129 @@ import java.util.Objects; ) public class JITProvisionCallbackHandler extends HttpServlet { private static final Log log = LogFactory.getLog(JITProvisionCallbackHandler.class); + private String keyManagerUrl; + private String JITProvisionCallbackUrl; + private String scope; + private String code; + private JITData JITInfo; @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 + 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; + + HttpSession session = request.getSession(false); + try { if (session == null) { response.sendError(HttpStatus.SC_UNAUTHORIZED); return; } + String state = request.getParameter("state"); 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); + code = request.getParameter("code"); + 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); + if (JITInfo.getApplicationUrl() != null) { + handleApplicationLogin(request, response, session); + return; + } + + response.sendRedirect(JITInfo.getRedirectUrl() + "?code=" + request.getParameter("code") + "&state=" + state); + } catch (IOException ex) { log.error("Error occurred while processing JIT provisioning callback request", ex); } } + + private void handleApplicationLogin(HttpServletRequest request, HttpServletResponse response, HttpSession session) { + keyManagerUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + + System.getProperty(HandlerConstants.IOT_KM_HOST_ENV_VAR) + + HandlerConstants.COLON + HandlerUtil.getKeyManagerPort(request.getScheme()); + try { + scope = session.getAttribute("scope").toString(); + persistAuthData(session, getToken()); + response.sendRedirect(JITInfo.getApplicationUrl()); + } catch (JITProvisionException | IOException ex) { + log.error("Error encountered while handling login request for " + JITInfo.getApplicationUrl()); + } + } + + /*** + * 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"); + } + + /*** + * Acquire token + * @return {@link JsonObject} Json object containing token data + * @throws JITProvisionException throws when error occurred while acquiring token + */ + private JsonObject getToken() throws JITProvisionException { + 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 JITProvisionException("Unexpected response status return for token acquiring request"); + } catch (IOException ex) { + throw new JITProvisionException("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.AUTHORIZATION, HandlerConstants.BASIC + JITInfo.getEncodedClientCredentials()); + tokenAcquiringRequest.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString()); + StringEntity payload = new StringEntity( + "grant_type=" + HandlerConstants.CODE_GRANT_TYPE + "&code=" + code + "&scope=" + scope + + "&redirect_uri=" + JITProvisionCallbackUrl, + 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.setRefreshToken(token.get("refresh_token").getAsString()); + authData.setClientId(JITInfo.getClientId()); + authData.setClientSecret(JITInfo.getClientSecret()); + authData.setEncodedClientApp(JITInfo.getEncodedClientCredentials()); + 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/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 9c1cf31a94..b3c23d4393 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 @@ -18,22 +18,18 @@ package io.entgra.device.mgt.core.ui.request.interceptor; -import com.google.gson.JsonElement; +import com.google.gson.Gson; +import com.google.gson.JsonArray; 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.beans.ServiceProvider; 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; @@ -52,9 +48,11 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.File; import java.io.IOException; import java.util.Base64; -import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; @WebServlet( @@ -66,42 +64,63 @@ import java.util.Objects; ) public class JITProvisionHandler extends HttpServlet { private static final Log log = LogFactory.getLog(JITProvisionHandler.class); + private static final String JITConfigurationPath = CarbonUtils.getCarbonConfigDirPath() + File.separator + "jit-config.xml"; + private static final Gson gson = new Gson(); + private static Map serviceProviders; private String tenantDomain; private String clientId; + private String clientSecret; private String JITServiceProviderName; private String encodedClientCredentials; - private String JITConfigurationPath; private String redirectUrl; + private String applicationUrl; private String state; - private static final Map tenantConfigs = new HashMap<>(); + private String scope; @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()); + String iotCoreUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + + System.getProperty(HandlerConstants.IOT_CORE_HOST_ENV_VAR) + + HandlerConstants.COLON + HandlerUtil.getCorePort(request.getScheme()); + String gatewayUrl = request.getScheme() + HandlerConstants.SCHEME_SEPARATOR + + System.getProperty(HandlerConstants.IOT_GW_HOST_ENV_VAR) + + HandlerConstants.COLON + HandlerUtil.getGatewayPort(request.getScheme()); 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; - JITConfigurationPath = CarbonUtils.getCarbonConfigDirPath() + File.separator + "jit-config.xml"; - String scope = "openid"; + String uiConfigUrl = iotCoreUrl + HandlerConstants.UI_CONFIG_ENDPOINT; + + HttpSession session = request.getSession(true); state = HandlerUtil.generateStateToken(); tenantDomain = request.getParameter("tenantDomain"); redirectUrl = request.getParameter("redirectUrl"); JITServiceProviderName = request.getParameter("sp"); + applicationUrl = request.getParameter("applicationUrl"); try { + JsonObject uiConfigJsonObject = HandlerUtil.getUIConfigAndPersistInSession(uiConfigUrl, gatewayUrl, session, response); + JsonArray scopeJson = uiConfigJsonObject.get("scopes").getAsJsonArray(); + scope = "openid " + HandlerUtil.getScopeString(scopeJson); + if (tenantDomain == null || JITServiceProviderName == null) { HandlerUtil.handleError(response, HttpStatus.SC_BAD_REQUEST); return; } + + if (serviceProviders == null) { + loadServiceProviders(); + } + if (!initializeJITConfigurations()) { HandlerUtil.handleError(response, HttpStatus.SC_SERVICE_UNAVAILABLE); return; } - persistJITData(request.getSession(true)); + persistJITData(session); response.sendRedirect(keyManagerUrl + HandlerConstants.AUTHORIZATION_ENDPOINT + "?response_type=code" + "&client_id=" + clientId + @@ -113,6 +132,32 @@ public class JITProvisionHandler extends HttpServlet { } } + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) { + tenantDomain = request.getParameter("tenantDomain"); + try { + if (tenantDomain == null) { + HandlerUtil.handleError(response, HttpStatus.SC_BAD_REQUEST); + return; + } + + if (serviceProviders == null) { + loadServiceProviders(); + } + + List supportingServiceProviders = serviceProviders.values().stream(). + filter(serviceProvider -> Objects.equals(tenantDomain, + serviceProvider.getTenantDomain())).map(ServiceProvider::getWhiteLabel).collect(Collectors.toList()); + ProxyResponse proxyResponse = new ProxyResponse(); + proxyResponse.setData(gson.toJson(supportingServiceProviders)); + proxyResponse.setStatus(ProxyResponse.Status.SUCCESS); + proxyResponse.setCode(HttpStatus.SC_OK); + HandlerUtil.handleSuccess(response, proxyResponse); + } catch (JITProvisionException | IOException ex) { + log.error("Error occurred while processing request", ex); + } + } + /*** * Retrieve JIT data from current session if session exists, otherwise build and return * @param session - {@link HttpSession} @@ -130,60 +175,49 @@ public class JITProvisionHandler extends HttpServlet { private void persistJITData(HttpSession session) { JITData JITInfo = getJITData(session); JITInfo.setEncodedClientCredentials(encodedClientCredentials); + JITInfo.setClientId(clientId); + JITInfo.setClientSecret(clientSecret); JITInfo.setTenantDomain(tenantDomain); JITInfo.setRedirectUrl(redirectUrl); JITInfo.setSp(JITServiceProviderName); + JITInfo.setApplicationUrl(applicationUrl); session.setMaxInactiveInterval(3600); session.setAttribute("state", state); + session.setAttribute("scope", scope); session.setAttribute(HandlerConstants.SESSION_JIT_DATA_KEY, JITInfo); } - /*** - * 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 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) && - Objects.equals(configElement.getAttributes().getNamedItem("name").getNodeValue(), - JITServiceProviderName)) { - 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 { + private void loadServiceProviders() throws JITProvisionException { + serviceProviders = new ConcurrentHashMap<>(); try { - 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); + File JITConfigurationFile = new File(JITConfigurationPath); + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document JITConfigurationDoc = documentBuilder.parse(JITConfigurationFile); + JITConfigurationDoc.getDocumentElement().normalize(); + NodeList serviceProviderConfiguration = JITConfigurationDoc.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; + ServiceProvider serviceProvider = new ServiceProvider(); + serviceProvider.setTenantDomain(configElement.getAttributes().getNamedItem("tenantDomain"). + getNodeValue()); + serviceProvider.setName(configElement.getAttributes().getNamedItem("name").getNodeValue()); + serviceProvider.setClientId(configElement.getElementsByTagName("ClientId").item(0). + getTextContent()); + serviceProvider.setClientSecret(configElement.getElementsByTagName("ClientSecret").item(0). + getTextContent()); + ServiceProvider.WhiteLabel whiteLabel = new ServiceProvider.WhiteLabel(configElement.getElementsByTagName("DisplayName").item(0). + getTextContent(), configElement.getElementsByTagName("IconUrl").item(0). + getTextContent()); + whiteLabel.setTenantDomain(serviceProvider.getTenantDomain()); + whiteLabel.setName(serviceProvider.getName()); + serviceProvider.setWhiteLabel(whiteLabel); + String spKey = serviceProvider.getName() + "@" + serviceProvider.getTenantDomain(); + serviceProviders.putIfAbsent(spKey, serviceProvider); + } } - 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"; throw new JITProvisionException(msg, ex); @@ -195,4 +229,20 @@ public class JITProvisionHandler extends HttpServlet { throw new JITProvisionException(msg, ex); } } + + /*** + * Initialize JIT configurations + * @return boolean true when successful initialization, otherwise false + */ + private boolean initializeJITConfigurations() { + ServiceProvider serviceProvider = serviceProviders.get(JITServiceProviderName + "@" + tenantDomain); + if (serviceProvider == null) { + return false; + } + clientId = serviceProvider.getClientId(); + clientSecret = serviceProvider.getClientSecret(); + String headerValue = clientId + ":" + clientSecret; + encodedClientCredentials = Base64.getEncoder().encodeToString(headerValue.getBytes()); + return true; + } } 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/SsoLoginHandler.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/SsoLoginHandler.java index 78fb91f7ae..5caedbb8ba 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/SsoLoginHandler.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/SsoLoginHandler.java @@ -130,12 +130,16 @@ public class SsoLoginHandler extends HttpServlet { String loginCallbackUrl = iotsCoreUrl + baseContextPath + HandlerConstants.SSO_LOGIN_CALLBACK; persistAuthSessionData(req, oAuthApp.getClientId(), oAuthApp.getClientSecret(), oAuthApp.getEncodedClientApp(), scopesSsoString, state); + JsonObject ssoConfigurations = uiConfigJsonObject.getAsJsonObject("ssoConfiguration"); + JsonArray authEndpointExtraQueryParam = ssoConfigurations.getAsJsonArray("authEndpointExtraQueryParam"); + String extraParamString = HandlerUtil.getAuthEndpointExtraQueryParamString(authEndpointExtraQueryParam); resp.sendRedirect(keyManagerUrl + HandlerConstants.AUTHORIZATION_ENDPOINT + "?response_type=code" + "&state=" + state + "&client_id=" + clientId + "&scope=openid " + scopesSsoString + - "&redirect_uri=" + loginCallbackUrl); + "&redirect_uri=" + loginCallbackUrl + + extraParamString); } catch (IOException e) { log.error("Error occurred while sending the response into the socket. ", e); } catch (JsonSyntaxException e) { 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 index 0e5f1769e7..ec21418f6f 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/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 @@ -25,6 +25,9 @@ public class JITData { private String redirectUrl; private String sp; private String encodedClientCredentials; + private String clientId; + private String clientSecret; + private String applicationUrl; public String getUsername() { return username; @@ -65,4 +68,28 @@ public class JITData { public void setEncodedClientCredentials(String encodedClientCredentials) { this.encodedClientCredentials = encodedClientCredentials; } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public String getApplicationUrl() { + return applicationUrl; + } + + public void setApplicationUrl(String applicationUrl) { + this.applicationUrl = applicationUrl; + } } 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/ServiceProvider.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/ServiceProvider.java new file mode 100644 index 0000000000..2f35e7d122 --- /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/ServiceProvider.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2018 - 2024, 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.beans; + +public class ServiceProvider { + private String tenantDomain; + private String name; + private String clientId; + private String clientSecret; + private WhiteLabel whiteLabel; + + public String getTenantDomain() { + return tenantDomain; + } + + public void setTenantDomain(String tenantDomain) { + this.tenantDomain = tenantDomain; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public WhiteLabel getWhiteLabel() { + return whiteLabel; + } + + public void setWhiteLabel(WhiteLabel whiteLabel) { + this.whiteLabel = whiteLabel; + } + + public static class WhiteLabel { + private String displayName; + private String iconUrl; + private String tenantDomain; + private String name; + + public WhiteLabel(String displayName, String iconUrl) { + this.displayName = displayName; + this.iconUrl = iconUrl; + } + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getIconUrl() { + return iconUrl; + } + + public void setIconUrl(String iconUrl) { + this.iconUrl = iconUrl; + } + + public String getTenantDomain() { + return tenantDomain; + } + + public void setTenantDomain(String tenantDomain) { + this.tenantDomain = tenantDomain; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} 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/HandlerUtil.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/HandlerUtil.java index 0d991c8693..4047a7479c 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/HandlerUtil.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/HandlerUtil.java @@ -799,4 +799,18 @@ public class HandlerUtil { public static String generateStateToken() { return new BigInteger(130, new SecureRandom()).toString(32); } + + public static String getAuthEndpointExtraQueryParamString(JsonArray extraQueryParams) { + StringBuilder stringBuilder = new StringBuilder(); + if (extraQueryParams != null && extraQueryParams.size() > 0) { + for (JsonElement param : extraQueryParams) { + if (param.isJsonObject()) { + JsonObject paramObj = param.getAsJsonObject(); + stringBuilder.append("&").append(paramObj.get("queryParam").getAsString()).append("="). + append(paramObj.get("paramValue").getAsString()); + } + } + } + return stringBuilder.toString(); + } } 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 1b72287a08..9c455ebb1c 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 @@ -39,6 +39,8 @@ \ No newline at end of file diff --git a/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/mdm-ui-config.xml b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/mdm-ui-config.xml index da4191cff2..9293499104 100644 --- a/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/mdm-ui-config.xml +++ b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf/mdm-ui-config.xml @@ -429,6 +429,16 @@ device-mgt + + + idpAvailability + false + + + idpLoginProviderEndpoint + https://localhost:9443/enroll-web-agent/idp/login + +