diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/public/conf/config.json b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/public/conf/config.json index 9264aac6d7..e953f2a4b1 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/public/conf/config.json +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/public/conf/config.json @@ -1,4 +1,5 @@ { + "appName": "publisher", "theme": { "logo": "https://entgra.io/assets/images/svg/logo.svg", "primaryColor": "rgb(24, 144, 255)", @@ -14,7 +15,10 @@ }, "loginUri": "/publisher-ui-request-handler/login", "logoutUri": "/publisher-ui-request-handler/logout", - "platform": "publisher" + "ssoLoginUri": "/publisher-ui-request-handler/ssoLogin", + "ssoLogoutUri": "/publisher-ui-request-handler/ssoLogout", + "platform": "publisher", + "appUiConfigUri": "/api/device-mgt-config/v1.0/configurations/ui-config" }, "defaultPlatformIcons": { "default": { diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/App.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/App.js index 78a387b656..33332fc89e 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/App.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/App.js @@ -23,6 +23,7 @@ import { BrowserRouter as Router, Redirect, Switch } from 'react-router-dom'; import axios from 'axios'; import { Layout, Spin, Result } from 'antd'; import ConfigContext from './components/ConfigContext'; +import { getUiConfig } from './services/utils/uiConfigHandler'; const { Content } = Layout; const loadingView = ( @@ -161,12 +162,29 @@ class App extends React.Component { const redirectUrl = encodeURI(window.location.href); const pageURL = window.location.pathname; const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1); - if (lastURLSegment !== 'login') { - window.location.href = - window.location.origin + `/publisher/login?redirect=${redirectUrl}`; - } else { - this.getAndroidEnterpriseToken(config); - } + getUiConfig(config).then(uiConfig => { + if (uiConfig !== undefined) { + if (uiConfig.isSsoEnable) { + window.location = + window.location.origin + + config.serverConfig.ssoLoginUri + + '?redirect=' + + window.location.origin + + pageURL; + } else if (lastURLSegment !== 'login') { + window.location.href = + window.location.origin + + `/${config.appName}/login?redirect=${redirectUrl}`; + } else { + this.getAndroidEnterpriseToken(config); + } + } else { + this.setState({ + loading: false, + error: true, + }); + } + }); } else { this.setState({ loading: false, diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/components/Logout/index.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/components/Logout/index.js index f41e654bcc..0ecbb604fa 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/components/Logout/index.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/scenes/Home/components/Logout/index.js @@ -21,6 +21,7 @@ import { LogoutOutlined } from '@ant-design/icons'; import { notification, Menu } from 'antd'; import axios from 'axios'; import { withConfigContext } from '../../../../components/ConfigContext'; +import { getUiConfig } from '../../../../services/utils/uiConfigHandler'; /* This class for call the logout api by sending request @@ -44,27 +45,38 @@ class Logout extends React.Component { inValid: false, }); - axios - .post(window.location.origin + config.serverConfig.logoutUri) - .then(res => { - // if the api call status is correct then user will logout and then it goes to login page - if (res.status === 200) { - window.location = window.location.origin + '/publisher/login'; - } - }) - .catch(function(error) { - if (error.hasOwnProperty('response') && error.response.status === 400) { - thisForm.setState({ - inValid: true, - }); + let logoutUri; + getUiConfig(config).then(uiConfig => { + if (uiConfig !== undefined) { + if (uiConfig.isSsoEnable) { + logoutUri = window.location.origin + config.serverConfig.ssoLogoutUri; } else { - notification.error({ - message: 'There was a problem', - duration: 0, - description: 'Error occurred while trying to logout.', - }); + logoutUri = window.location.origin + config.serverConfig.logoutUri; } - }); + axios + .post(logoutUri) + .then(res => { + // if the api call status is correct then user + // will logout and then it goes to login page + if (res.status === 200) { + window.location = + window.location.origin + `/${config.appName}/login`; + } + }) + .catch(function(error) { + notification.error({ + message: 'There was a problem', + duration: 0, + description: 'Error occurred while trying to logout.', + }); + }); + } else { + this.setState({ + loading: false, + error: true, + }); + } + }); }; render() { diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/services/utils/uiConfigHandler.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/services/utils/uiConfigHandler.js new file mode 100644 index 0000000000..77fd691c37 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/services/utils/uiConfigHandler.js @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020. Entgra (Pvt) Ltd, https://entgra.io + * All Rights Reserved. + * + * Unauthorized copying/redistribution of this file, via any medium + * is strictly prohibited. + * Proprietary and confidential. + * + * Licensed under the Entgra Commercial License, + * Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * 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 may obtain a copy of the License at + * https://entgra.io/licenses/entgra-commercial/1.0 + */ + +import axios from 'axios'; +import { notification } from 'antd'; + +export const getUiConfig = config => { + return axios + .get(window.location.origin + config.serverConfig.appUiConfigUri) + .then(res => { + return res.data; + }) + .catch(error => { + notification.error({ + message: 'There was a problem', + duration: 0, + description: 'Error occurred while trying to load UI configurations.', + }); + }); +}; diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/public/conf/config.json b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/public/conf/config.json index 4850da1d91..7e36b866c5 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/public/conf/config.json +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/public/conf/config.json @@ -1,4 +1,5 @@ { + "appName": "store", "theme": { "type": "default", "value": "lightBaseTheme", @@ -17,7 +18,10 @@ }, "loginUri": "/store-ui-request-handler/login", "logoutUri": "/store-ui-request-handler/logout", - "platform": "store" + "ssoLoginUri": "/store-ui-request-handler/ssoLogin", + "ssoLogoutUri": "/store-ui-request-handler/ssoLogout", + "platform": "store", + "appUiConfigUri": "/api/device-mgt-config/v1.0/configurations/ui-config" }, "defaultPlatformIcons": { "default": { diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/App.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/App.js index 6ade8a306d..e78a5983d8 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/App.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/App.js @@ -21,8 +21,9 @@ import 'antd/dist/antd.less'; import RouteWithSubRoutes from './components/RouteWithSubRoutes'; import { BrowserRouter as Router, Redirect, Switch } from 'react-router-dom'; import axios from 'axios'; -import { Layout, Spin, Result } from 'antd'; +import { Layout, Result, Spin } from 'antd'; import ConfigContext from './components/context/ConfigContext'; +import { getUiConfig } from './services/utils/uiConfigHandler'; const { Content } = Layout; const loadingView = ( @@ -138,15 +139,32 @@ class App extends React.Component { const redirectUrl = encodeURI(window.location.href); const pageURL = window.location.pathname; const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1); - if (lastURLSegment !== 'login') { - window.location.href = - window.location.origin + `/store/login?redirect=${redirectUrl}`; - } else { - this.setState({ - loading: false, - config: config, - }); - } + getUiConfig(config).then(uiConfig => { + if (uiConfig !== undefined) { + if (uiConfig.isSsoEnable) { + window.location = + window.location.origin + + config.serverConfig.ssoLoginUri + + '?redirect=' + + window.location.origin + + pageURL; + } else if (lastURLSegment !== 'login') { + window.location.href = + window.location.origin + + `/${config.appName}/login?redirect=${redirectUrl}`; + } else { + this.setState({ + loading: false, + config: config, + }); + } + } else { + this.setState({ + loading: false, + error: true, + }); + } + }); } else { this.setState({ loading: false, diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/scenes/Home/components/Logout/index.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/scenes/Home/components/Logout/index.js index d2c5e106eb..84bc6c0c94 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/scenes/Home/components/Logout/index.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/scenes/Home/components/Logout/index.js @@ -22,6 +22,7 @@ import { Menu } from 'antd'; import axios from 'axios'; import { withConfigContext } from '../../../../components/context/ConfigContext'; import { handleApiError } from '../../../../services/utils/errorHandler'; +import { getUiConfig } from '../../../../services/utils/uiConfigHandler'; /* This class for call the logout api by sending request @@ -45,20 +46,34 @@ class Logout extends React.Component { inValid: false, }); - axios - .post(window.location.origin + config.serverConfig.logoutUri) - .then(res => { - // if the api call status is correct then user will logout and then it goes to login page - if (res.status === 200) { - window.location = window.location.origin + '/store/login'; + let logoutUri; + getUiConfig(config).then(uiConfig => { + if (uiConfig !== undefined) { + if (uiConfig.isSsoEnable) { + logoutUri = window.location.origin + config.serverConfig.ssoLogoutUri; + } else { + logoutUri = window.location.origin + config.serverConfig.logoutUri; } - }) - .catch(function(error) { - handleApiError( - error, - 'Error occurred while trying to get your review.', - ); - }); + axios + .post(logoutUri) + .then(res => { + // if the api call status is correct then user + // will logout and then it goes to login page + if (res.status === 200) { + window.location = + window.location.origin + `/${config.appName}/login`; + } + }) + .catch(function(error) { + handleApiError(error, 'Error occurred while trying to logout.'); + }); + } else { + this.setState({ + loading: false, + error: true, + }); + } + }); }; render() { diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/services/utils/uiConfigHandler.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/services/utils/uiConfigHandler.js new file mode 100644 index 0000000000..77fd691c37 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.store.ui/react-app/src/services/utils/uiConfigHandler.js @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020. Entgra (Pvt) Ltd, https://entgra.io + * All Rights Reserved. + * + * Unauthorized copying/redistribution of this file, via any medium + * is strictly prohibited. + * Proprietary and confidential. + * + * Licensed under the Entgra Commercial License, + * Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * 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 may obtain a copy of the License at + * https://entgra.io/licenses/entgra-commercial/1.0 + */ + +import axios from 'axios'; +import { notification } from 'antd'; + +export const getUiConfig = config => { + return axios + .get(window.location.origin + config.serverConfig.appUiConfigUri) + .then(res => { + return res.data; + }) + .catch(error => { + notification.error({ + message: 'There was a problem', + duration: 0, + description: 'Error occurred while trying to load UI configurations.', + }); + }); +}; diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.sign-in.consent-do/consent-do.hbs b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.sign-in.consent-do/consent-do.hbs index 56f05e6956..22869a4828 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.sign-in.consent-do/consent-do.hbs +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.sign-in.consent-do/consent-do.hbs @@ -29,7 +29,7 @@
-
+

By selecting following attributes I agree to share them with the above service provider.

{{#unless singleMandatoryClaim}}
@@ -60,9 +60,9 @@

Mandatory claims are marked with an asterisk ( * )

- + - +
@@ -72,4 +72,4 @@ {{/zone}} {{#zone "bottomJs"}} {{js "js/script.js"}} -{{/zone}} \ No newline at end of file +{{/zone}} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.sign-in.consent-do/consent-do.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.sign-in.consent-do/consent-do.js index 346b81df46..e3e4224eda 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.sign-in.consent-do/consent-do.js +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.sign-in.consent-do/consent-do.js @@ -1,7 +1,21 @@ function onRequest(context) { var Encode = Packages.org.owasp.encoder.Encode; var viewModel = {}; - viewModel.appName = Encode.forHtml(request.getParameter("sp")); + + // if sp is received, its a saml request or else its oidc + if(request.getParameter("sp") !== null) { + viewModel.appName = Encode.forHtml(request.getParameter("sp")); + viewModel.action = "/commonauth"; + viewModel.sessionDataKey = Encode.forHtmlAttribute(request.getParameter("sessionDataKey")); + viewModel.sessionDataKeyName = "sessionDataKey"; + viewModel.ssoProtocol = "saml"; + } else { + viewModel.appName = Encode.forHtml(request.getParameter("application")); + viewModel.action = "../oauth2/authorize"; + viewModel.sessionDataKey = Encode.forHtmlAttribute(request.getParameter("sessionDataKeyConsent")); + viewModel.sessionDataKeyName = "sessionDataKeyConsent"; + viewModel.ssoProtocol = "oidc"; + } var mandatoryClaims = []; var requestedClaims = []; var singleMandatoryClaim = false; @@ -36,6 +50,5 @@ function onRequest(context) { viewModel.mandatoryClaims = mandatoryClaims; viewModel.requestedClaims = requestedClaims; viewModel.singleMandatoryClaim = singleMandatoryClaim; - viewModel.sessionDataKey = Encode.forHtmlAttribute(request.getParameter("sessionDataKey")); return viewModel; -} \ No newline at end of file +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.sign-in.consent-do/public/js/script.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.sign-in.consent-do/public/js/script.js index 8588c004a9..7f6fff8d8b 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.sign-in.consent-do/public/js/script.js +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.sign-in.consent-do/public/js/script.js @@ -1,9 +1,13 @@ -function approved() { +function approved(ssoProtocol) { var mandatoryClaimCBs = $(".mandatory-claim"); var checkedMandatoryClaimCBs = $(".mandatory-claim:checked"); if (checkedMandatoryClaimCBs.length == mandatoryClaimCBs.length) { - document.getElementById('consent').value = "approve"; + if(ssoProtocol === "saml") { + document.getElementById('consent').value = "approve"; + } else if(ssoProtocol === "oidc") { + document.getElementById('consent').value = "approveAlways"; + } document.getElementById("consentForm").submit(); } else { $("#modal_claim_validation").modal(); @@ -32,4 +36,4 @@ $(document).ready(function () { $("#consent_select_all").prop("checked", false); } }); -}); \ No newline at end of file +}); 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