From f31befb3429bcb1057960d4e8771b3553366ac02 Mon Sep 17 00:00:00 2001 From: Vigneshan Date: Fri, 4 Dec 2020 10:06:00 +0530 Subject: [PATCH] Implement oidc based sso among all web apps --- .../ui/request/interceptor/LoginHandler.java | 118 ++------ .../interceptor/SsoLoginCallbackHandler.java | 95 ++++++ .../request/interceptor/SsoLoginHandler.java | 285 ++++++++++++++++++ .../request/interceptor/SsoLogoutHandler.java | 70 +++++ .../interceptor/util/HandlerConstants.java | 12 + .../request/interceptor/util/HandlerUtil.java | 151 ++++++++-- .../src/main/resources/conf/mdm-ui-config.xml | 2 +- 7 files changed, 608 insertions(+), 125 deletions(-) create mode 100644 components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginCallbackHandler.java create mode 100644 components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginHandler.java create mode 100644 components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLogoutHandler.java diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/LoginHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/LoginHandler.java index 53faa63eda..3ef9d9d4a9 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/LoginHandler.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/LoginHandler.java @@ -27,12 +27,10 @@ import io.entgra.ui.request.interceptor.beans.AuthData; import io.entgra.ui.request.interceptor.exceptions.LoginException; import io.entgra.ui.request.interceptor.util.HandlerConstants; import io.entgra.ui.request.interceptor.util.HandlerUtil; -import org.apache.commons.lang.StringUtils; 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.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; @@ -71,66 +69,31 @@ public class LoginHandler extends HttpServlet { //setting session to expiry in 5 minutes httpSession.setMaxInactiveInterval(Math.toIntExact(HandlerConstants.TIMEOUT)); - HttpGet uiConfigEndpoint = new HttpGet(uiConfigUrl); - ProxyResponse uiConfigResponse = HandlerUtil.execute(uiConfigEndpoint); - String executorResponse = uiConfigResponse.getExecutorResponse(); - if (!StringUtils.isEmpty(executorResponse) && executorResponse - .contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { - log.error("Error occurred while getting UI configurations by invoking " + uiConfigUrl); - HandlerUtil.handleError(resp, uiConfigResponse); - return; - } + JsonObject uiConfigJsonObject = HandlerUtil.getUIConfigAndPersistInSession(uiConfigUrl, gatewayUrl, httpSession, resp); - String uiConfig = uiConfigResponse.getData(); - if (uiConfig == null){ - log.error("UI config retrieval is failed, and didn't find UI configuration for App manager."); - HandlerUtil.handleError(resp, null); - return; - } - JsonParser jsonParser = new JsonParser(); - JsonElement uiConfigJsonElement = jsonParser.parse(uiConfigResponse.getData()); - JsonObject uiConfigJsonObject = null; - if (uiConfigJsonElement.isJsonObject()) { - uiConfigJsonObject = uiConfigJsonElement.getAsJsonObject(); - httpSession.setAttribute(HandlerConstants.UI_CONFIG_KEY, uiConfigJsonObject); - httpSession.setAttribute(HandlerConstants.PLATFORM, gatewayUrl); - } - if (uiConfigJsonObject == null) { - log.error( - "Either UI config json element is not an json object or converting rom json element to json object is failed."); - HandlerUtil.handleError(resp, null); - return; - } - - boolean isSsoEnable = uiConfigJsonObject.get("isSsoEnable").getAsBoolean(); JsonArray tags = uiConfigJsonObject.get("appRegistration").getAsJsonObject().get("tags").getAsJsonArray(); JsonArray scopes = uiConfigJsonObject.get("scopes").getAsJsonArray(); - if (isSsoEnable) { - log.debug("SSO is enabled"); - } else { - // default login - HttpPost apiRegEndpoint = new HttpPost(gatewayUrl + HandlerConstants.APP_REG_ENDPOINT); - apiRegEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + Base64.getEncoder() - .encodeToString((username + HandlerConstants.COLON + password).getBytes())); - apiRegEndpoint.setHeader(HTTP.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); - apiRegEndpoint.setEntity(constructAppRegPayload(tags)); + HttpPost apiRegEndpoint = new HttpPost(gatewayUrl + HandlerConstants.APP_REG_ENDPOINT); + apiRegEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + Base64.getEncoder() + .encodeToString((username + HandlerConstants.COLON + password).getBytes())); + apiRegEndpoint.setHeader(HTTP.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); + apiRegEndpoint.setEntity(HandlerUtil.constructAppRegPayload(tags, HandlerConstants.PUBLISHER_APPLICATION_NAME, username, password)); - ProxyResponse clientAppResponse = HandlerUtil.execute(apiRegEndpoint); + ProxyResponse clientAppResponse = HandlerUtil.execute(apiRegEndpoint); - if (clientAppResponse.getCode() == HttpStatus.SC_UNAUTHORIZED){ - HandlerUtil.handleError(resp, clientAppResponse); - return; - } - if (clientAppResponse.getCode() == HttpStatus.SC_CREATED && getTokenAndPersistInSession(req, resp, - clientAppResponse.getData(), scopes)) { - ProxyResponse proxyResponse = new ProxyResponse(); - proxyResponse.setCode(HttpStatus.SC_OK); - HandlerUtil.handleSuccess(resp, proxyResponse); - return; - } - HandlerUtil.handleError(resp, null); + if (clientAppResponse.getCode() == HttpStatus.SC_UNAUTHORIZED) { + HandlerUtil.handleError(resp, clientAppResponse); + return; + } + if (clientAppResponse.getCode() == HttpStatus.SC_CREATED && getTokenAndPersistInSession(req, resp, + clientAppResponse.getData(), scopes)) { + ProxyResponse proxyResponse = new ProxyResponse(); + proxyResponse.setCode(HttpStatus.SC_OK); + HandlerUtil.handleSuccess(resp, proxyResponse); + return; } + HandlerUtil.handleError(resp, null); } catch (IOException e) { log.error("Error occurred while sending the response into the socket. ", e); } catch (JsonSyntaxException e) { @@ -141,6 +104,7 @@ public class LoginHandler extends HttpServlet { } /*** + * Generates token from token endpoint and persists them inside the session * * @param req - {@link HttpServletRequest} * @param clientAppResult - clientAppResult @@ -148,7 +112,7 @@ public class LoginHandler extends HttpServlet { * @throws LoginException - login exception throws when getting token result */ private boolean getTokenAndPersistInSession(HttpServletRequest req, HttpServletResponse resp, - String clientAppResult, JsonArray scopes) throws LoginException { + String clientAppResult, JsonArray scopes) throws LoginException { JsonParser jsonParser = new JsonParser(); try { JsonElement jClientAppResult = jsonParser.parse(clientAppResult); @@ -167,7 +131,7 @@ public class LoginHandler extends HttpServlet { return false; } String tokenResult = tokenResultResponse.getData(); - if (tokenResult == null){ + if (tokenResult == null) { log.error("Invalid token response is received."); HandlerUtil.handleError(resp, tokenResultResponse); return false; @@ -197,24 +161,6 @@ public class LoginHandler extends HttpServlet { } } - /*** - * - * @param scopes - scope Json Array and it is retrieved by reading UI config. - * @return string value of the defined scopes - */ - private String getScopeString(JsonArray scopes) { - if (scopes != null && scopes.size() > 0) { - StringBuilder builder = new StringBuilder(); - for (JsonElement scope : scopes) { - String tmpScope = scope.getAsString() + " "; - builder.append(tmpScope); - } - return builder.toString(); - } else { - return null; - } - } - /*** * * @param req - {@link HttpServletRequest} @@ -239,22 +185,7 @@ public class LoginHandler extends HttpServlet { } /*** - * - * @param tags - tags which are retrieved by reading app manager configuration - * @return {@link StringEntity} of the payload to create the client application - */ - private StringEntity constructAppRegPayload(JsonArray tags) { - JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty(HandlerConstants.APP_NAME_KEY, HandlerConstants.PUBLISHER_APPLICATION_NAME); - jsonObject.addProperty(HandlerConstants.USERNAME, username); - jsonObject.addProperty(HandlerConstants.PASSWORD, password); - jsonObject.addProperty("isAllowedToAllDomains", "false"); - jsonObject.add(HandlerConstants.TAGS_KEY, tags); - String payload = jsonObject.toString(); - return new StringEntity(payload, ContentType.APPLICATION_JSON); - } - - /*** + * Generates tokens by invoking token endpoint * * @param encodedClientApp - Base64 encoded clientId:clientSecret. * @param scopes - Scopes which are retrieved by reading application-mgt configuration @@ -265,7 +196,7 @@ public class LoginHandler extends HttpServlet { HttpPost tokenEndpoint = new HttpPost(gatewayUrl + HandlerConstants.TOKEN_ENDPOINT); tokenEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + encodedClientApp); tokenEndpoint.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString()); - String scopeString = getScopeString(scopes); + String scopeString = HandlerUtil.getScopeString(scopes); if (scopeString != null) { scopeString = scopeString.trim(); @@ -274,7 +205,8 @@ public class LoginHandler extends HttpServlet { } StringEntity tokenEPPayload = new StringEntity( - "grant_type=password&username=" + username + "&password=" + password + "&scope=" + scopeString, + "grant_type=" + HandlerConstants.PASSWORD_GRANT_TYPE + "&username=" + username + "&password=" + + password + "&scope=" + scopeString, ContentType.APPLICATION_FORM_URLENCODED); tokenEndpoint.setEntity(tokenEPPayload); return HandlerUtil.execute(tokenEndpoint); diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginCallbackHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginCallbackHandler.java new file mode 100644 index 0000000000..3d18be2ab1 --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginCallbackHandler.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020, 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.ui.request.interceptor; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.entgra.ui.request.interceptor.beans.AuthData; +import io.entgra.ui.request.interceptor.util.HandlerConstants; +import io.entgra.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.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.wso2.carbon.device.application.mgt.common.ProxyResponse; + +import javax.servlet.annotation.MultipartConfig; +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; + +@MultipartConfig +@WebServlet("/ssoLoginCallback") +public class SsoLoginCallbackHandler extends HttpServlet { + private static final Log log = LogFactory.getLog(SsoLoginCallbackHandler.class); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + String code = req.getParameter("code"); + HttpSession session = req.getSession(false); + String scope = session.getAttribute("scope").toString(); + String iotsCorePort = System.getProperty("iot.core.https.port"); + + if (HandlerConstants.HTTP_PROTOCOL.equals(req.getScheme())) { + iotsCorePort = System.getProperty("iot.core.http.port"); + } + + String gatewayUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty("iot.gateway.host") + + HandlerConstants.COLON + HandlerUtil.getGatewayPort(req.getScheme()); + String iotsCoreUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty("iot.core.host") + + HandlerConstants.COLON + iotsCorePort; + + HttpPost tokenEndpoint = new HttpPost(gatewayUrl + HandlerConstants.TOKEN_ENDPOINT); + tokenEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + session.getAttribute("encodedClientApp")); + tokenEndpoint.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString()); + + String loginCallbackUrl = iotsCoreUrl + req.getContextPath() + HandlerConstants.SSO_LOGIN_CALLBACK; + + StringEntity tokenEPPayload = new StringEntity( + "grant_type=" + HandlerConstants.CODE_GRANT_TYPE + "&code=" + code + "&state=&scope=" + scope + + "&redirect_uri=" + loginCallbackUrl, + ContentType.APPLICATION_FORM_URLENCODED); + tokenEndpoint.setEntity(tokenEPPayload); + ProxyResponse tokenResultResponse = HandlerUtil.execute(tokenEndpoint); + + JsonParser jsonParser = new JsonParser(); + + JsonElement jTokenResult = jsonParser.parse(tokenResultResponse.getData()); + if (jTokenResult.isJsonObject()) { + JsonObject jTokenResultAsJsonObject = jTokenResult.getAsJsonObject(); + + AuthData authData = new AuthData(); + authData.setClientId(session.getAttribute("clientId").toString()); + authData.setClientSecret(session.getAttribute("clientSecret").toString()); + authData.setEncodedClientApp(session.getAttribute("encodedClientApp").toString()); + authData.setAccessToken(jTokenResultAsJsonObject.get("access_token").getAsString()); + authData.setRefreshToken(jTokenResultAsJsonObject.get("refresh_token").getAsString()); + authData.setScope(jTokenResultAsJsonObject.get("scope").getAsString()); + session.setAttribute(HandlerConstants.SESSION_AUTH_DATA_KEY, authData); + + resp.sendRedirect(session.getAttribute("redirectUrl").toString()); + } + } +} diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginHandler.java new file mode 100644 index 0000000000..1ea7d030e1 --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLoginHandler.java @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2020, 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.ui.request.interceptor; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import io.entgra.ui.request.interceptor.util.HandlerConstants; +import io.entgra.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.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.protocol.HTTP; +import org.json.JSONArray; +import org.json.JSONObject; +import org.w3c.dom.Document; +import org.wso2.carbon.device.application.mgt.common.ProxyResponse; +import org.xml.sax.SAXException; + +import javax.servlet.annotation.MultipartConfig; +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; + +@MultipartConfig +@WebServlet("/ssoLogin") +public class SsoLoginHandler extends HttpServlet { + private static final Log log = LogFactory.getLog(SsoLoginHandler.class); + + private static String adminUsername; + private static String adminPassword; + private static String gatewayUrl; + private static String iotsCoreUrl; + private static String encodedClientApp; + private static String applicationId; + private static String baseContextPath; + + private JsonObject uiConfigJsonObject; + private HttpSession httpSession; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + dynamicClientRegistration(req, resp); + String clientId = httpSession.getAttribute("clientId").toString(); + JsonArray scopesSsoJson = uiConfigJsonObject.get("scopes").getAsJsonArray(); + String scopesSsoString = HandlerUtil.getScopeString(scopesSsoJson); + String loginCallbackUrl = iotsCoreUrl + baseContextPath + HandlerConstants.SSO_LOGIN_CALLBACK; + resp.sendRedirect(iotsCoreUrl + HandlerConstants.AUTHORIZATION_ENDPOINT + + "?response_type=code" + + "&client_id=" + clientId + + "&state=" + + "&scope=openid " + scopesSsoString + + "&redirect_uri=" + loginCallbackUrl); + } + + /*** + * Handles DCR and updates grant types of the application + * before redirecting to the authorization endpoint. + * + * @param req {@link HttpServletRequest} + * @param resp {@link HttpServletResponse} + */ + private void dynamicClientRegistration(HttpServletRequest req, HttpServletResponse resp) { + try { + File userMgtConf = new File("conf/user-mgt.xml"); + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(userMgtConf); + + adminUsername = doc.getElementsByTagName("UserName").item(0).getTextContent(); + adminPassword = doc.getElementsByTagName("Password").item(0).getTextContent(); + + baseContextPath = req.getContextPath(); + String applicationName = baseContextPath.substring(1, baseContextPath.indexOf("-ui-request-handler")); + + String iotsCorePort = System.getProperty("iot.core.https.port"); + + if (HandlerConstants.HTTP_PROTOCOL.equals(req.getScheme())) { + iotsCorePort = System.getProperty("iot.core.http.port"); + } + + gatewayUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty("iot.gateway.host") + + HandlerConstants.COLON + HandlerUtil.getGatewayPort(req.getScheme()); + iotsCoreUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty("iot.core.host") + + HandlerConstants.COLON + iotsCorePort; + String uiConfigUrl = iotsCoreUrl + HandlerConstants.UI_CONFIG_ENDPOINT; + + httpSession = req.getSession(false); + if (httpSession != null) { + httpSession.invalidate(); + } + + httpSession = req.getSession(true); + uiConfigJsonObject = HandlerUtil.getUIConfigAndPersistInSession(uiConfigUrl, gatewayUrl, httpSession, resp); + + JsonArray tags = uiConfigJsonObject.get("appRegistration").getAsJsonObject().get("tags").getAsJsonArray(); + JsonArray scopes = uiConfigJsonObject.get("scopes").getAsJsonArray(); + + // Register the client application + HttpPost apiRegEndpoint = new HttpPost(gatewayUrl + HandlerConstants.APP_REG_ENDPOINT); + String encodedAdminCredentials = Base64.getEncoder() + .encodeToString((adminUsername + HandlerConstants.COLON + adminPassword).getBytes()); + apiRegEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + + encodedAdminCredentials); + apiRegEndpoint.setHeader(HTTP.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); + apiRegEndpoint.setEntity(HandlerUtil.constructAppRegPayload(tags, applicationName, adminUsername, adminPassword)); + + ProxyResponse clientAppResponse = HandlerUtil.execute(apiRegEndpoint); + + if (clientAppResponse.getCode() == HttpStatus.SC_UNAUTHORIZED) { + HandlerUtil.handleError(resp, clientAppResponse); + } + + if (clientAppResponse.getCode() == HttpStatus.SC_CREATED) { + JsonParser jsonParser = new JsonParser(); + JsonElement jClientAppResult = jsonParser.parse(clientAppResponse.getData()); + if (jClientAppResult.isJsonObject()) { + JsonObject jClientAppResultAsJsonObject = jClientAppResult.getAsJsonObject(); + String clientId = jClientAppResultAsJsonObject.get("client_id").getAsString(); + String clientSecret = jClientAppResultAsJsonObject.get("client_secret").getAsString(); + encodedClientApp = Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes()); + String redirectUrl = req.getParameter("redirect"); + httpSession = req.getSession(false); + httpSession.setAttribute("clientId", clientId); + httpSession.setAttribute("clientSecret", clientSecret); + httpSession.setAttribute("encodedClientApp", encodedClientApp); + httpSession.setAttribute("scope", HandlerUtil.getScopeString(scopes)); + httpSession.setAttribute("redirectUrl", redirectUrl); + } + } + + // Get the details of the registered application + String getApplicationEndpointUrl = iotsCoreUrl + HandlerConstants.APIM_APPLICATIONS_ENDPOINT + + "?query=" + applicationName; + HttpGet getApplicationEndpoint = new HttpGet(getApplicationEndpointUrl); + getApplicationEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + + getAccessToken(resp, encodedClientApp)); + + ProxyResponse getApplicationResponse = HandlerUtil.execute(getApplicationEndpoint); + + if (getApplicationResponse.getCode() == HttpStatus.SC_UNAUTHORIZED) { + HandlerUtil.handleError(resp, getApplicationResponse); + return; + } + + if (getApplicationResponse.getCode() == HttpStatus.SC_OK) { + JsonParser jsonParser = new JsonParser(); + JsonElement jAppResult = jsonParser.parse(getApplicationResponse.getData()); + if (jAppResult.isJsonObject()) { + JsonObject jClientAppResultAsJsonObject = jAppResult.getAsJsonObject(); + JsonArray appList = jClientAppResultAsJsonObject.getAsJsonArray("list"); + JsonObject app; + for (JsonElement appJson : appList) { + app = appJson.getAsJsonObject(); + if (app.get("name").getAsString().equals(applicationName)) { + applicationId = app.get("applicationId").getAsString(); + break; + } + } + } + } + + // Update the grant types of the application + String url = iotsCoreUrl + HandlerConstants.APIM_APPLICATIONS_ENDPOINT + applicationId + "/keys/PRODUCTION"; + HttpPut updateApplicationGrantTypesEndpoint = new HttpPut(url); + updateApplicationGrantTypesEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + + getAccessToken(resp, encodedClientApp)); + updateApplicationGrantTypesEndpoint.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); + updateApplicationGrantTypesEndpoint.setEntity(constructAppGrantTypeUpdatePayload()); + + ProxyResponse updateApplicationGrantTypesEndpointResponse = HandlerUtil.execute(updateApplicationGrantTypesEndpoint); + + if (updateApplicationGrantTypesEndpointResponse.getCode() == HttpStatus.SC_UNAUTHORIZED) { + HandlerUtil.handleError(resp, updateApplicationGrantTypesEndpointResponse); + return; + } + + if (updateApplicationGrantTypesEndpointResponse.getCode() == HttpStatus.SC_OK) { + return; + } + + HandlerUtil.handleError(resp, null); + } catch (IOException e) { + log.error("Error occurred while sending the response into the socket. ", e); + } catch (JsonSyntaxException e) { + log.error("Error occurred while parsing the response. ", e); + } catch (ParserConfigurationException | SAXException e) { + log.error("Error while parsing xml file.", e); + } + } + + /*** + * Generates payload for application grant_type update payload + * + * @return {@link StringEntity} of the payload to update application grant type + */ + private StringEntity constructAppGrantTypeUpdatePayload() { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("supportedGrantTypes", new JSONArray(new Object[]{HandlerConstants.CODE_GRANT_TYPE, + HandlerConstants.REFRESH_TOKEN_GRANT_TYPE, HandlerConstants.PASSWORD_GRANT_TYPE})); + jsonObject.put(HandlerConstants.CALLBACK_URL_KEY, iotsCoreUrl + baseContextPath + HandlerConstants.SSO_LOGIN_CALLBACK); + String payload = jsonObject.toString(); + return new StringEntity(payload, ContentType.APPLICATION_JSON); + } + + /*** + * Generates tokens using password grant_type by invoking token endpoint + * + * @param encodedClientApp - Base64 encoded clientId:clientSecret. + * @return Invoke token endpoint and return the response as string. + * @throws IOException IO exception throws if an error occurred when invoking token endpoint + */ + private ProxyResponse getTokenResult(String encodedClientApp) throws IOException { + HttpPost tokenEndpoint = new HttpPost(gatewayUrl + HandlerConstants.TOKEN_ENDPOINT); + tokenEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + encodedClientApp); + tokenEndpoint.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString()); + + StringEntity tokenEPPayload = new StringEntity( + "grant_type=" + HandlerConstants.PASSWORD_GRANT_TYPE + "&username=" + adminUsername + "&password=" + adminPassword + + "&scope=apim:api_view apim:api_create apim:api_publish apim:subscribe", + ContentType.APPLICATION_FORM_URLENCODED); + tokenEndpoint.setEntity(tokenEPPayload); + return HandlerUtil.execute(tokenEndpoint); + } + + /*** + * Retrieves and returns access token + * + * @param resp - Http Servlet Response + * @param encodedClientApp - Base64 encoded clientId:clientSecret. + * @return Returns access token + * @throws IOException IO exception throws if an error occurred when invoking token endpoint + */ + private String getAccessToken(HttpServletResponse resp, String encodedClientApp) throws IOException { + ProxyResponse tokenResultResponse = getTokenResult(encodedClientApp); + + if (tokenResultResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { + log.error("Error occurred while invoking the API to get token data."); + HandlerUtil.handleError(resp, tokenResultResponse); + } + String tokenResult = tokenResultResponse.getData(); + if (tokenResult == null) { + log.error("Invalid token response is received."); + HandlerUtil.handleError(resp, tokenResultResponse); + } + + JsonParser jsonParser = new JsonParser(); + JsonElement jTokenResult = jsonParser.parse(tokenResult); + + JsonObject jTokenResultAsJsonObject = jTokenResult.getAsJsonObject(); + return jTokenResultAsJsonObject.get("access_token").getAsString(); + } +} diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLogoutHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLogoutHandler.java new file mode 100644 index 0000000000..a3d8f0bcc0 --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/SsoLogoutHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020, 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.ui.request.interceptor; + +import io.entgra.ui.request.interceptor.util.HandlerConstants; +import io.entgra.ui.request.interceptor.util.HandlerUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpStatus; +import org.wso2.carbon.device.application.mgt.common.ProxyResponse; + +import javax.servlet.annotation.MultipartConfig; +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.servlet.http.Cookie; +import java.io.IOException; + +@MultipartConfig +@WebServlet("/ssoLogout") +public class SsoLogoutHandler extends HttpServlet { + private static final Log log = LogFactory.getLog(SsoLogoutHandler.class); + + protected void doPost(HttpServletRequest req, HttpServletResponse resp) { + for (String path : HandlerConstants.SSO_LOGOUT_COOKIE_PATHS) { + removeCookie(HandlerConstants.JSESSIONID_KEY, System.getProperty("iot.core.host"), path, resp); + } + removeCookie(HandlerConstants.COMMON_AUTH_ID_KEY, System.getProperty("iot.core.host"), "/", resp); + ProxyResponse proxyResponse = new ProxyResponse(); + proxyResponse.setCode(HttpStatus.SC_OK); + + HttpSession session = req.getSession(false); + if (session != null) { + session.invalidate(); + } + try { + HandlerUtil.handleSuccess(resp, proxyResponse); + } catch (IOException e) { + log.error("Error occurred when processing logout request.", e); + } + } + + private static void removeCookie(String cookieName, String domain, + String path, HttpServletResponse response) { + Cookie cookie = new Cookie(cookieName, ""); + cookie.setPath(path); + cookie.setDomain(domain); + cookie.setValue(null); + cookie.setMaxAge(0); + response.addCookie(cookie); + } +} diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerConstants.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerConstants.java index b691eaa315..8c0e79c2a6 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerConstants.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerConstants.java @@ -24,7 +24,10 @@ public class HandlerConstants { public static final String UI_CONFIG_ENDPOINT = "/api/device-mgt-config/v1.0/configurations/ui-config"; public static final String TOKEN_ENDPOINT = "/token"; public static final String INTROSPECT_ENDPOINT = "/oauth2/introspect"; + public static final String AUTHORIZATION_ENDPOINT = "/oauth2/authorize"; + public static final String APIM_APPLICATIONS_ENDPOINT = "/api/am/store/v0.12/applications/"; public static final String LOGIN_PAGE = "/login"; + public static final String SSO_LOGIN_CALLBACK = "/ssoLoginCallback"; public static final String BASIC = "Basic "; public static final String BEARER = "Bearer "; public static final String TAGS_KEY = "tags"; @@ -32,6 +35,10 @@ public class HandlerConstants { public static final String SESSION_AUTH_DATA_KEY = "authInfo"; public static final String SESSION_DEFAULT_AUTH_DATA_KEY = "defaultAuthInfo"; public static final String UI_CONFIG_KEY = "ui-config"; + public static final String CALLBACK_URL_KEY = "callbackUrl"; + public static final String IS_ALLOWED_TO_ALL_DOMAINS_KEY = "isAllowedToAllDomains"; + public static final String JSESSIONID_KEY = "JSESSIONID"; + public static final String COMMON_AUTH_ID_KEY = "commonAuthId"; public static final String PLATFORM = "platform"; public static final String USERNAME = "username"; public static final String PASSWORD = "password"; @@ -40,6 +47,11 @@ public class HandlerConstants { public static final String TOKEN_IS_EXPIRED = "ACCESS_TOKEN_IS_EXPIRED"; public static final String REPORTS = "Reports"; public static final String APP_NAME = "App-Name"; + public static final String[] SSO_LOGOUT_COOKIE_PATHS = new String[]{"/", "/entgra-ui-request-handler", + "/store-ui-request-handler", "/publisher-ui-request-handler", "/mdm-reports-ui-request-handler", "/devicemgt"}; + public static final String CODE_GRANT_TYPE = "authorization_code"; + public static final String REFRESH_TOKEN_GRANT_TYPE = "refresh_token"; + public static final String PASSWORD_GRANT_TYPE = "password"; public static final String SCHEME_SEPARATOR = "://"; public static final String COLON = ":"; diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerUtil.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerUtil.java index 592806f338..5c80a88eb0 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerUtil.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/HandlerUtil.java @@ -19,15 +19,21 @@ package io.entgra.ui.request.interceptor.util; import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.Consts; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.json.JSONException; @@ -36,6 +42,7 @@ import org.wso2.carbon.device.application.mgt.common.ProxyResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -113,37 +120,37 @@ public class HandlerUtil { * @param statusCode Provide status code, e.g:- 400, 401, 500 etc * @return relative status code key for given status code. */ - public static String getStatusKey (int statusCode){ + public static String getStatusKey(int statusCode) { String statusCodeKey; switch (statusCode) { - case HttpStatus.SC_INTERNAL_SERVER_ERROR: - statusCodeKey = "internalServerError"; - break; - case HttpStatus.SC_BAD_REQUEST: - statusCodeKey = "badRequest"; - break; - case HttpStatus.SC_UNAUTHORIZED: - statusCodeKey = "unauthorized"; - break; - case HttpStatus.SC_FORBIDDEN: - statusCodeKey = "forbidden"; - break; - case HttpStatus.SC_NOT_FOUND: - statusCodeKey = "notFound"; - break; - case HttpStatus.SC_METHOD_NOT_ALLOWED: - statusCodeKey = "methodNotAllowed"; - break; - case HttpStatus.SC_NOT_ACCEPTABLE: - statusCodeKey = "notAcceptable"; - break; - case HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE: - statusCodeKey = "unsupportedMediaType"; - break; - default: - statusCodeKey = "defaultPage"; - break; + case HttpStatus.SC_INTERNAL_SERVER_ERROR: + statusCodeKey = "internalServerError"; + break; + case HttpStatus.SC_BAD_REQUEST: + statusCodeKey = "badRequest"; + break; + case HttpStatus.SC_UNAUTHORIZED: + statusCodeKey = "unauthorized"; + break; + case HttpStatus.SC_FORBIDDEN: + statusCodeKey = "forbidden"; + break; + case HttpStatus.SC_NOT_FOUND: + statusCodeKey = "notFound"; + break; + case HttpStatus.SC_METHOD_NOT_ALLOWED: + statusCodeKey = "methodNotAllowed"; + break; + case HttpStatus.SC_NOT_ACCEPTABLE: + statusCodeKey = "notAcceptable"; + break; + case HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE: + statusCodeKey = "unsupportedMediaType"; + break; + default: + statusCodeKey = "defaultPage"; + break; } return statusCodeKey; } @@ -156,7 +163,7 @@ public class HandlerUtil { */ public static void handleError(HttpServletResponse resp, ProxyResponse proxyResponse) throws IOException { Gson gson = new Gson(); - if (proxyResponse == null){ + if (proxyResponse == null) { proxyResponse = new ProxyResponse(); proxyResponse.setCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); proxyResponse.setExecutorResponse(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil @@ -178,7 +185,7 @@ public class HandlerUtil { * Return Success Response. */ public static void handleSuccess(HttpServletResponse resp, ProxyResponse proxyResponse) throws IOException { - if (proxyResponse == null){ + if (proxyResponse == null) { handleError(resp, null); return; } @@ -188,7 +195,7 @@ public class HandlerUtil { JSONObject response = new JSONObject(); String responseData = proxyResponse.getData(); - if (!StringUtils.isEmpty(responseData)){ + if (!StringUtils.isEmpty(responseData)) { try { JSONObject responseDataJsonObj = new JSONObject(responseData); response.put("data", responseDataJsonObj); @@ -205,6 +212,7 @@ public class HandlerUtil { /** * Get gateway port according to request received scheme + * * @param scheme https or https * @return {@link String} gateway port */ @@ -218,6 +226,7 @@ public class HandlerUtil { /** * Get core port according to request received scheme + * * @param scheme https or https * @return {@link String} gateway port */ @@ -231,6 +240,7 @@ public class HandlerUtil { /** * Retrieve Http client based on hostname verification. + * * @return {@link CloseableHttpClient} http client */ public static CloseableHttpClient getHttpClient() { @@ -273,4 +283,83 @@ public class HandlerUtil { } return urlBuilder.toString(); } + + /*** + * Constructs the application registration payload for DCR. + * + * @param tags - tags which are retrieved by reading app manager configuration + * @param username - username provided from login form or admin username + * @param password - password provided from login form or admin password + * @return {@link StringEntity} of the payload to create the client application + */ + public static StringEntity constructAppRegPayload(JsonArray tags, String appName, String username, String password) { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty(HandlerConstants.APP_NAME_KEY, appName); + jsonObject.addProperty(HandlerConstants.USERNAME, username); + jsonObject.addProperty(HandlerConstants.PASSWORD, password); + jsonObject.addProperty(HandlerConstants.IS_ALLOWED_TO_ALL_DOMAINS_KEY, "false"); + jsonObject.add(HandlerConstants.TAGS_KEY, tags); + String payload = jsonObject.toString(); + return new StringEntity(payload, ContentType.APPLICATION_JSON); + } + + /*** + * Retrieves UI configuration and returns as Json. + * + * @param uiConfigUrl - UI configurations endpoint URL + * @param gatewayUrl - gateway endpoint URL + * @param httpSession - current active HttpSession + * @param resp - HttpServletResponse + * @return {@link JsonObject} of UI configurations + */ + public static JsonObject getUIConfigAndPersistInSession(String uiConfigUrl, String gatewayUrl, HttpSession httpSession, + HttpServletResponse resp) throws IOException { + HttpGet uiConfigEndpoint = new HttpGet(uiConfigUrl); + ProxyResponse uiConfigResponse = HandlerUtil.execute(uiConfigEndpoint); + String executorResponse = uiConfigResponse.getExecutorResponse(); + if (!StringUtils.isEmpty(executorResponse) && executorResponse + .contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { + log.error("Error occurred while getting UI configurations by invoking " + uiConfigUrl); + HandlerUtil.handleError(resp, uiConfigResponse); + } + + if (uiConfigResponse.getData() == null) { + log.error("UI config retrieval is failed, and didn't find UI configuration for App manager."); + HandlerUtil.handleError(resp, null); + } + JsonParser jsonParser = new JsonParser(); + + JsonElement uiConfigJsonElement = jsonParser.parse(uiConfigResponse.getData()); + JsonObject uiConfigJsonObject = null; + if (uiConfigJsonElement.isJsonObject()) { + uiConfigJsonObject = uiConfigJsonElement.getAsJsonObject(); + if (uiConfigJsonObject == null) { + log.error( + "Either UI config json element is not an json object or converting rom json element to json object is failed."); + HandlerUtil.handleError(resp, null); + } + httpSession.setAttribute(HandlerConstants.UI_CONFIG_KEY, uiConfigJsonObject); + httpSession.setAttribute(HandlerConstants.PLATFORM, gatewayUrl); + } + return uiConfigJsonObject; + } + + /*** + * Converts scopes from JsonArray to string with space separated values. + * + * @param scopes - scope Json Array and it is retrieved by reading UI config. + * @return string value of the defined scopes + */ + public static String getScopeString(JsonArray scopes) { + if (scopes != null && scopes.size() > 0) { + StringBuilder builder = new StringBuilder(); + for (JsonElement scope : scopes) { + String tmpScope = scope.getAsString() + " "; + builder.append(tmpScope); + } + return builder.toString(); + } else { + return null; + } + } } diff --git a/features/device-mgt/org.wso2.carbon.device.mgt.basics.feature/src/main/resources/conf/mdm-ui-config.xml b/features/device-mgt/org.wso2.carbon.device.mgt.basics.feature/src/main/resources/conf/mdm-ui-config.xml index f079a3bb48..78858d50b6 100644 --- a/features/device-mgt/org.wso2.carbon.device.mgt.basics.feature/src/main/resources/conf/mdm-ui-config.xml +++ b/features/device-mgt/org.wso2.carbon.device.mgt.basics.feature/src/main/resources/conf/mdm-ui-config.xml @@ -19,7 +19,7 @@ true - false + true application_management