diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/exception/RefererNotValid.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/exception/RefererNotValid.java new file mode 100644 index 0000000000..0a36f34b23 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/exception/RefererNotValid.java @@ -0,0 +1,7 @@ +package org.wso2.carbon.device.mgt.jaxrs.exception; + +public class RefererNotValid extends Exception { + public RefererNotValid(String msg) { + super(msg); + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/ReportManagementService.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/ReportManagementService.java index ee3e9354dd..c5eeccab2c 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/ReportManagementService.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/ReportManagementService.java @@ -17,6 +17,7 @@ */ package org.wso2.carbon.device.mgt.jaxrs.service.api; +import com.google.gson.JsonObject; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -37,12 +38,16 @@ import org.wso2.carbon.device.mgt.jaxrs.util.Constants; import javax.ws.rs.Consumes; import javax.ws.rs.GET; +import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; import java.util.List; @SwaggerDefinition( @@ -78,6 +83,89 @@ import java.util.List; @Consumes(MediaType.APPLICATION_JSON) public interface ReportManagementService { + @POST + @Path("/grafana/api/ds/query") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "POST", + value = "Grafana query API proxy", + tags = "Analytics", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:view") + }) + } + ) + Response queryDatasource(JsonObject body, @Context HttpHeaders headers, @Context UriInfo requestUriInfo); + + @POST + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @Path("/grafana/api/frontend-metrics") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "POST", + value = "Grafana frontend-metric API proxy", + tags = "Analytics", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:view") + }) + } + ) + Response frontendMetrics(JsonObject body, @Context HttpHeaders headers, @Context UriInfo requestUriInfo); + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/grafana/api/dashboards/uid/{uid}") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "POST", + value = "Grafana dashboard details API proxy", + tags = "Analytics", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:view") + }) + } + ) + Response getDashboard(@Context HttpHeaders headers, @Context UriInfo requestUriInfo) throws ClassNotFoundException; + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @Path("/grafana/api/annotations") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "GET", + value = "Grafana annotations API proxy", + tags = "Analytics", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:view") + }) + } + ) + Response getAnnotations(@Context HttpHeaders headers, @Context UriInfo requestUriInfo) throws ClassNotFoundException; + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/grafana/api/alerts/states-for-dashboard") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "POST", + value = "Get Grafana alert states for dashboard details API proxy", + tags = "Analytics", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:view") + }) + } + ) + Response getAlertStateForDashboards(@Context HttpHeaders headers, @Context UriInfo requestUriInfo) throws ClassNotFoundException; + @GET @Path("/devices") @ApiOperation( diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/ReportManagementServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/ReportManagementServiceImpl.java index eb95311868..1342bc1801 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/ReportManagementServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/ReportManagementServiceImpl.java @@ -26,25 +26,38 @@ import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.PaginationResult; import org.wso2.carbon.device.mgt.common.exceptions.BadRequestException; +import org.wso2.carbon.device.mgt.common.exceptions.DBConnectionException; import org.wso2.carbon.device.mgt.common.exceptions.DeviceTypeNotFoundException; +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; import org.wso2.carbon.device.mgt.common.exceptions.ReportManagementException; +import org.wso2.carbon.device.mgt.core.grafana.mgt.bean.GrafanaPanelIdentifier; +import org.wso2.carbon.device.mgt.core.grafana.mgt.exception.MaliciousQueryAttempt; +import org.wso2.carbon.device.mgt.core.grafana.mgt.util.GrafanaUtil; import org.wso2.carbon.device.mgt.core.report.mgt.Constants; import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceList; import org.wso2.carbon.device.mgt.common.ReportFiltersList; import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse; +import org.wso2.carbon.device.mgt.jaxrs.exception.RefererNotValid; import org.wso2.carbon.device.mgt.jaxrs.service.api.ReportManagementService; +import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.GrafanaRequestHandlerUtil; import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.RequestValidationUtil; import org.wso2.carbon.device.mgt.jaxrs.util.DeviceMgtAPIUtils; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; +import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.io.IOException; +import java.sql.SQLException; import java.util.List; /** @@ -57,6 +70,85 @@ public class ReportManagementServiceImpl implements ReportManagementService { private static final Log log = LogFactory.getLog(ReportManagementServiceImpl.class); + @POST + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @Path("/grafana/api/ds/query") + @Override + public Response queryDatasource(JsonObject body, @Context HttpHeaders headers, @Context UriInfo requestUriInfo) { + try { + GrafanaPanelIdentifier panelIdentifier = GrafanaRequestHandlerUtil.getPanelIdentifier(headers); + GrafanaUtil.getGrafanaQueryService().buildSafeQuery(body, panelIdentifier.getDashboardId(), panelIdentifier.getPanelId(), requestUriInfo.getRequestUri()); + return GrafanaRequestHandlerUtil.proxyPassPostRequest(body, requestUriInfo, panelIdentifier.getOrgId()); + } catch (MaliciousQueryAttempt e) { + return Response.status(Response.Status.BAD_REQUEST).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(e.getMessage()).build()).build(); + } catch (GrafanaManagementException e) { + return GrafanaRequestHandlerUtil.constructInternalServerError(e, e.getMessage()); + } catch (RefererNotValid e) { + return GrafanaRequestHandlerUtil.constructInvalidReferer(); + } catch (SQLException | IOException | DBConnectionException e) { + log.error(e); + return GrafanaRequestHandlerUtil.constructInternalServerError(e, e.getMessage()); + } + } + + @POST + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @Path("/grafana/api/frontend-metrics") + @Override + public Response frontendMetrics(JsonObject body, @Context HttpHeaders headers, @Context UriInfo requestUriInfo) { + return proxyPassPostRequest(body, headers, requestUriInfo); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/grafana/api/dashboards/uid/{uid}") + @Override + public Response getDashboard(@Context HttpHeaders headers, @Context UriInfo requestUriInfo) { + return proxyPassGetRequest(headers, requestUriInfo); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @Path("/grafana/api/annotations") + @Override + public Response getAnnotations(@Context HttpHeaders headers, @Context UriInfo requestUriInfo) { + return proxyPassGetRequest(headers, requestUriInfo); + } + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/grafana/api/alerts/states-for-dashboard") + @Override + public Response getAlertStateForDashboards(@Context HttpHeaders headers, @Context UriInfo requestUriInfo) { + return proxyPassGetRequest(headers, requestUriInfo); + } + + public Response proxyPassGetRequest(HttpHeaders headers, UriInfo requestUriInfo) { + try { + GrafanaPanelIdentifier panelIdentifier = GrafanaRequestHandlerUtil.getPanelIdentifier(headers); + return GrafanaRequestHandlerUtil.proxyPassGetRequest(requestUriInfo, panelIdentifier.getOrgId()); + } catch (RefererNotValid e) { + return GrafanaRequestHandlerUtil.constructInvalidReferer(); + } catch (GrafanaManagementException e) { + return GrafanaRequestHandlerUtil.constructInternalServerError(e, e.getMessage()); + } + } + + public Response proxyPassPostRequest(JsonObject body, HttpHeaders headers, UriInfo requestUriInfo) { + try { + GrafanaPanelIdentifier panelIdentifier = GrafanaRequestHandlerUtil.getPanelIdentifier(headers); + return GrafanaRequestHandlerUtil.proxyPassPostRequest(body, requestUriInfo, panelIdentifier.getOrgId()); + } catch (RefererNotValid e) { + return GrafanaRequestHandlerUtil.constructInvalidReferer(); + } catch (GrafanaManagementException e) { + return GrafanaRequestHandlerUtil.constructInternalServerError(e, e.getMessage()); + } + } + + @GET @Path("/devices") @Override diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/util/GrafanaRequestHandlerUtil.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/util/GrafanaRequestHandlerUtil.java new file mode 100644 index 0000000000..9b44e17296 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/util/GrafanaRequestHandlerUtil.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.jaxrs.service.impl.util; + +import com.google.gson.JsonObject; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; +import org.wso2.carbon.device.mgt.core.common.util.HttpUtil; +import org.wso2.carbon.device.mgt.core.grafana.mgt.bean.GrafanaPanelIdentifier; +import org.wso2.carbon.device.mgt.core.grafana.mgt.exception.GrafanaEnvVariablesNotDefined; +import org.wso2.carbon.device.mgt.core.grafana.mgt.util.GrafanaConstants; +import org.wso2.carbon.device.mgt.core.grafana.mgt.util.GrafanaUtil; +import org.wso2.carbon.device.mgt.core.report.mgt.Constants; +import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse; +import org.wso2.carbon.device.mgt.jaxrs.exception.RefererNotValid; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.util.List; +import java.util.Map; + +public class GrafanaRequestHandlerUtil { + + private static final Log log = LogFactory.getLog(GrafanaRequestHandlerUtil.class); + + public static Response proxyPassGetRequest(UriInfo requestUriInfo, String orgId) throws GrafanaEnvVariablesNotDefined { + HttpGet grafanaGetReq = new HttpGet(); + return forwardRequestToGrafanaEndpoint(grafanaGetReq, requestUriInfo, orgId); + } + + public static Response proxyPassPostRequest(JsonObject body, UriInfo requestUriInfo, String orgId) + throws GrafanaEnvVariablesNotDefined { + HttpPost grafanaPostReq = new HttpPost(); + try { + setRequestEntity(grafanaPostReq, body); + } catch (UnsupportedEncodingException e) { + String errorMsg = "Error occurred while parsing body"; + log.error(errorMsg, e); + return Response.status(Response.Status.BAD_REQUEST).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(errorMsg).build()).build(); + } + return forwardRequestToGrafanaEndpoint(grafanaPostReq, requestUriInfo, orgId); + } + + private static Response forwardRequestToGrafanaEndpoint(HttpRequestBase requestBase, UriInfo requestUriInfo, String orgId) + throws GrafanaEnvVariablesNotDefined { + URI grafanaUri = generateGrafanaUri(requestUriInfo); + requestBase.setURI(grafanaUri); + requestBase.setHeader(GrafanaConstants.X_GRAFANA_ORG_ID_HEADER, orgId); + try(CloseableHttpClient client = HttpClients.createDefault()) { + HttpResponse grafanaResponse = invokeGrafanaAPI(client, requestBase); + String grafanaResponseBody = HttpUtil.getResponseString(grafanaResponse); + return Response.status(Response.Status.OK).entity(grafanaResponseBody). + header(HttpHeaders.CONTENT_TYPE, HttpUtil.getContentType(grafanaResponse)).build(); + } catch (IOException e) { + String msg = "Error occurred while calling Grafana API"; + log.error(msg, e); + return Response.serverError().entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); + } catch (GrafanaManagementException e) { + String err = "Error occurred while retrieving Grafana configuration"; + log.error(err, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(err).build()).build(); + } + } + + public static HttpResponse invokeGrafanaAPI(HttpClient client, HttpRequestBase request) throws IOException, GrafanaManagementException { + setBasicAuthHeader(request); + return client.execute(request); + } + + public static void setBasicAuthHeader(HttpRequestBase request) throws GrafanaManagementException { + String basicAuth = GrafanaUtil.getBasicAuthBase64Header(); + request.setHeader(HttpHeaders.AUTHORIZATION, basicAuth); + } + + public static URI generateGrafanaUri(UriInfo requestUriInfo) throws GrafanaEnvVariablesNotDefined { + String base = GrafanaUtil.getGrafanaHTTPBase(requestUriInfo.getRequestUri().getScheme()); + String grafanaRequestPath = getGrafanaRequestPathWQuery(requestUriInfo); + return HttpUtil.createURI(GrafanaUtil.generateGrafanaUrl(grafanaRequestPath, base)); + } + + public static String getGrafanaRequestPathWQuery(UriInfo requestUriInfo) { + String contextPath = "/reports/grafana"; + String path = requestUriInfo.getPath().substring(contextPath.length()); + String queryParam = requestUriInfo.getRequestUri().getRawQuery(); + if (queryParam != null) { + path += Constants.URI_QUERY_SEPARATOR + queryParam; + } + return path; + } + + public static GrafanaPanelIdentifier getPanelIdentifier(HttpHeaders headers) throws RefererNotValid { + String referer = headers.getHeaderString(GrafanaConstants.REFERER_HEADER); + if(referer == null) { + String errMsg = "Request does not contain Referer header"; + log.error(errMsg); + throw new RefererNotValid(errMsg); + } + GrafanaPanelIdentifier panelIdentifier = GrafanaUtil.getPanelIdentifierFromReferer(referer); + if(panelIdentifier.getDashboardId() == null || + panelIdentifier.getPanelId() == null || panelIdentifier.getOrgId() == null) { + String errMsg = "Referer must contain dashboardId, panelId and orgId"; + log.error(errMsg); + throw new RefererNotValid(errMsg); + } + return panelIdentifier; + } + + public static void setRequestEntity(HttpPost postRequest, JsonObject body) throws UnsupportedEncodingException { + StringEntity bodyEntity = new StringEntity(body.toString()); + bodyEntity.setContentType(MediaType.APPLICATION_JSON); + postRequest.setEntity(bodyEntity); + } + + public static Response constructInvalidReferer() { + String errorMsg = "Request does not contain a valid Referer header"; + return Response.status(Response.Status.BAD_REQUEST).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(errorMsg).build()).build(); + } + public static Response constructInternalServerError(Exception e, String errMsg) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(errMsg).build()).build(); + } + + public static void copyHeadersToGrafanaRequest(HttpRequestBase grafanaRequest, HttpHeaders headers) { + Map> headerValues = headers.getRequestHeaders(); + for (String key : headerValues.keySet()) { + if (!key.equals(HttpHeaders.AUTHORIZATION) && !key.equals(HttpHeaders.CONTENT_LENGTH)) { + for (String value : headerValues.get(key)) { + grafanaRequest.setHeader(key, value); + } + } + } + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/exceptions/GrafanaManagementException.java b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/exceptions/GrafanaManagementException.java new file mode 100644 index 0000000000..331f52a209 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/exceptions/GrafanaManagementException.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.common.exceptions; + +public class GrafanaManagementException extends Exception{ + + private static final long serialVersionUID = -3951279311829079297L; + + public GrafanaManagementException(String msg, Exception nestedEx) { + super(msg, nestedEx); + } + public GrafanaManagementException(String msg) { + super(msg); + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/pom.xml b/components/device-mgt/org.wso2.carbon.device.mgt.core/pom.xml index 04b1e73c79..710cda2d84 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/pom.xml +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/pom.xml @@ -338,6 +338,10 @@ org.wso2.carbon.devicemgt io.entgra.server.bootup.heartbeat.beacon + + com.google.guava + guava + diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/common/util/HttpUtil.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/common/util/HttpUtil.java new file mode 100644 index 0000000000..5a21aaa122 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/common/util/HttpUtil.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2020, Entgra (pvt) Ltd. (http://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 org.wso2.carbon.device.mgt.core.common.util; + +import org.apache.commons.lang.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.entity.ContentType; +import org.apache.http.util.EntityUtils; +import org.wso2.carbon.device.mgt.core.report.mgt.Constants; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class HttpUtil { + + public static URI createURI(String uriString) { + uriString = uriString.replace(" ", "%20"); + return URI.create(uriString); + } + + public static String getRequestSubPathFromEnd(URI requestUri, int position) { + if (requestUri.getPath() != null) { + String[] pathList = requestUri.getPath().split("/"); + if (pathList.length - 1 >= position) { + return pathList[pathList.length - 1 - position]; + } + } + return null; + } + + public static String getRequestSubPath(String fullPath, int position) { + String[] pathList = fullPath.split("/"); + if (pathList.length - 1 > position) { + String path = pathList[position + 1]; + if(path.contains(Constants.URI_QUERY_SEPARATOR)) { + path = path.substring(0, path.indexOf(Constants.URI_QUERY_SEPARATOR)); + } + return path; + } + return null; + } + + public static String getResponseString(HttpResponse response) throws IOException { + return EntityUtils.toString(response.getEntity()); + } + + public static boolean isQueryParamExist(String param, URI request) { + Map> queryMap = getQueryMap(request); + return queryMap.containsKey(param); + } + public static String getFirstQueryValue(String param, Map> queryMap) { + List valueList = queryMap.get(param); + String firstValue = null; + if(valueList != null) { + firstValue = valueList.get(0); + } + return firstValue; + } + public static Map> getQueryMap(String uri) { + String query = getQueryFromURIPath(uri); + Map> map = new HashMap<>(); + if (query != null) { + String[] params = query.split("&"); + for (String param : params) { + String[] paramArr = param.split("="); + if (paramArr.length == 2) { + String name = paramArr[0]; + String value = paramArr[1]; + if (!map.containsKey(name)) { + List valueList = new ArrayList<>(); + map.put(name, valueList); + } + map.get(name).add(value); + } + } + } + return map; + } + public static Map> getQueryMap(URI request) { + String query = request.getQuery(); + Map> map = new HashMap<>(); + if (query != null) { + String[] params = query.split("&"); + for (String param : params) { + String[] paramArr = param.split("="); + if (paramArr.length == 2) { + String name = paramArr[0]; + String value = paramArr[1]; + if (!map.containsKey(name)) { + List valueList = new ArrayList<>(); + map.put(name, valueList); + } + map.get(name).add(value); + } + } + } + return map; + } + public static String getQueryFromURIPath(String uri) { + String query = null; + if (uri.length() > "?".length() && uri.contains("?")) { + query = uri.substring(uri.lastIndexOf("?") + "?".length()); + } + if (StringUtils.isEmpty(query)) { + query = null; + } + return query; + } + + public static String getContentType(HttpResponse response) { + ContentType contentType = ContentType.getOrDefault(response.getEntity()); + return contentType.getMimeType(); + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/bean/GrafanaPanelIdentifier.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/bean/GrafanaPanelIdentifier.java new file mode 100644 index 0000000000..950edfa1cb --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/bean/GrafanaPanelIdentifier.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.bean; + +public class GrafanaPanelIdentifier { + + private String orgId; + private String dashboardUID; + private String panelId; + + public GrafanaPanelIdentifier(String orgId, String dashboardUID, String panelId) { + this.orgId = orgId; + this.dashboardUID = dashboardUID; + this.panelId = panelId; + } + + public String getOrgId() { + return orgId; + } + + public void setOrgId(String orgId) { + this.orgId = orgId; + } + + public String getDashboardId() { + return dashboardUID; + } + + public void setDashboardId(String dashboardId) { + this.dashboardUID = dashboardId; + } + + public String getPanelId() { + return panelId; + } + + public void setPanelId(String panelId) { + this.panelId = panelId; + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/config/GrafanaConfiguration.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/config/GrafanaConfiguration.java new file mode 100644 index 0000000000..25a1302728 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/config/GrafanaConfiguration.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021, 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 org.wso2.carbon.device.mgt.core.grafana.mgt.config; + +import org.wso2.carbon.device.mgt.core.grafana.mgt.config.xml.bean.CacheConfiguration; +import org.wso2.carbon.device.mgt.core.grafana.mgt.config.xml.bean.User; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.List; + +@XmlRootElement(name = "GrafanaConfiguration") +public class GrafanaConfiguration { + + private User adminUser; + private List caches; + + @XmlElement(name = "AdminUser") + public User getAdminUser() { + return adminUser; + } + + public void setAdminUser(User user) { + this.adminUser = user; + } + + + @XmlElementWrapper(name = "Cache") + @XmlElement(name = "CacheConfiguration") + public List getCaches() { + return caches; + } + + public CacheConfiguration getCacheByName(String cacheName) { + for (CacheConfiguration cache : caches) { + if (cache.getName().equals(cacheName)) { + return cache; + } + } + return null; + } + + public void setCaches(List caches) { + this.caches = caches; + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/config/GrafanaConfigurationManager.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/config/GrafanaConfigurationManager.java new file mode 100644 index 0000000000..d4f51315c9 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/config/GrafanaConfigurationManager.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.config; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Document; +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; +import org.wso2.carbon.device.mgt.core.grafana.mgt.util.GrafanaConstants; +import org.wso2.carbon.utils.CarbonUtils; + +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.File; + +public class GrafanaConfigurationManager { + + private static final Log log = LogFactory.getLog(GrafanaConfigurationManager.class); + private static GrafanaConfigurationManager grafanaConfigurationManager; + private GrafanaConfiguration grafanaConfiguration; + private static final String GRAFANA_CONFIG_PATH = CarbonUtils.getCarbonConfigDirPath() + File.separator + + GrafanaConstants.CONFIG_XML_NAME; + + public static GrafanaConfigurationManager getInstance() { + if (grafanaConfigurationManager == null) { + synchronized (GrafanaConfigurationManager.class) { + if (grafanaConfigurationManager == null) { + grafanaConfigurationManager = new GrafanaConfigurationManager(); + } + } + } + return grafanaConfigurationManager; + } + + /** + * Initialize the Grafana Configuration through the provided configuration location + * @param configLocation has the path of the configuration file + * @throws GrafanaManagementException throws when there are any errors during the initialization of + * Grafana configuration + */ + public synchronized void initConfig(String configLocation) throws GrafanaManagementException { + try { + File smsConfig = new File(configLocation); + Document doc = convertXMLToDocument(smsConfig); + + /* Un-marshaling Grafana configuration */ + JAXBContext smsContext = JAXBContext.newInstance(GrafanaConfiguration.class); + Unmarshaller unmarshaller = smsContext.createUnmarshaller(); + this.grafanaConfiguration = (GrafanaConfiguration) unmarshaller.unmarshal(doc); + } catch (JAXBException e) { + String msg = "Error occurred while initializing Grafana config '" + configLocation + "'"; + log.error(msg, e); + throw new GrafanaManagementException(msg, e); + } + } + + /** + * Initialize the Grafana Configuration through the grafana-config.xml file in the GRAFANA_CONFIG_PATH + * @throws GrafanaManagementException throws when there are any errors during the initialization of config + */ + public void initConfig() throws GrafanaManagementException { + this.initConfig(GRAFANA_CONFIG_PATH); + } + + public GrafanaConfiguration getGrafanaConfiguration() throws GrafanaManagementException { + if (grafanaConfiguration != null) { + return grafanaConfiguration; + } + initConfig(); + return grafanaConfiguration; + } + + private static Document convertXMLToDocument(File file) throws GrafanaManagementException { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + try { + factory.setFeature(GrafanaConstants.XML.FEATURES_DISALLOW_DOCTYPE_DECL, true); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + DocumentBuilder docBuilder = factory.newDocumentBuilder(); + return docBuilder.parse(file); + } catch (Exception e) { + String errMsg = "Error occurred while parsing file, while converting to a org.w3c.dom.Document"; + log.error(errMsg, e); + throw new GrafanaManagementException(errMsg, e); + } + } + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/config/xml/bean/CacheConfiguration.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/config/xml/bean/CacheConfiguration.java new file mode 100644 index 0000000000..9ab509b7bb --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/config/xml/bean/CacheConfiguration.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021, 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 org.wso2.carbon.device.mgt.core.grafana.mgt.config.xml.bean; + +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; + +public class CacheConfiguration { + + private String name; + private int capacity; + + @XmlAttribute(name = "name") + public String getName() { + return name; + } + + @XmlElement(name = "Capacity") + public int getCapacity() { + return capacity; + } + + public void setName(String name) { + this.name = name; + } + + public void setCapacity(int capacity) { + this.capacity = capacity; + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/config/xml/bean/User.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/config/xml/bean/User.java new file mode 100644 index 0000000000..8fd868fd86 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/config/xml/bean/User.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.config.xml.bean; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name="AdminUser") +public class User { + private String username; + private String password; + + @XmlElement(name="Username") + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + @XmlElement(name="Password") + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/DashboardNotFound.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/DashboardNotFound.java new file mode 100644 index 0000000000..597b44d346 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/DashboardNotFound.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.exception; + +public class DashboardNotFound extends GrafanaAPIException { + + private static final long serialVersionUID = -2111271331930070297L; + + public DashboardNotFound(String errMsg) { + super(errMsg); + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/DatasourceNotFound.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/DatasourceNotFound.java new file mode 100644 index 0000000000..6e7a187f06 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/DatasourceNotFound.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.exception; + +public class DatasourceNotFound extends GrafanaAPIException { + + private static final long serialVersionUID = -3171279332930270227L; + + public DatasourceNotFound(String errMsg) { + super(errMsg); + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/GrafanaAPIException.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/GrafanaAPIException.java new file mode 100644 index 0000000000..2a6b85c31e --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/GrafanaAPIException.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.exception; + +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; + +public class GrafanaAPIException extends GrafanaManagementException { + + private static final long serialVersionUID = -3921249462930270227L; + + public GrafanaAPIException(String errMsg) { + super(errMsg); + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/GrafanaEnvVariablesNotDefined.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/GrafanaEnvVariablesNotDefined.java new file mode 100644 index 0000000000..5bccbcf3b7 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/GrafanaEnvVariablesNotDefined.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.exception; + +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; + +public class GrafanaEnvVariablesNotDefined extends GrafanaManagementException { + + private static final long serialVersionUID = -3444449462330270237L; + + public GrafanaEnvVariablesNotDefined(String errMsg) { + super(errMsg); + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/MaliciousQueryAttempt.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/MaliciousQueryAttempt.java new file mode 100644 index 0000000000..133a4037e9 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/MaliciousQueryAttempt.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.exception; + +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; + +public class MaliciousQueryAttempt extends GrafanaManagementException { + + private static final long serialVersionUID = -3171279331930070297L; + + public MaliciousQueryAttempt(String msg) { + super(msg); + } + + public MaliciousQueryAttempt(String msg, Exception nestedException) { + super(msg, nestedException); + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/PanelNotFound.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/PanelNotFound.java new file mode 100644 index 0000000000..e3d191be30 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/PanelNotFound.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.exception; + +public class PanelNotFound extends GrafanaAPIException { + + private static final long serialVersionUID = -3471479441930070297L; + public PanelNotFound(String errMsg) { + super(errMsg); + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/QueryMisMatch.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/QueryMisMatch.java new file mode 100644 index 0000000000..b2a6244bf0 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/QueryMisMatch.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.exception; + +public class QueryMisMatch extends MaliciousQueryAttempt { + + private static final long serialVersionUID = -3171279334939076294L; + + public QueryMisMatch(String msg) { + super(msg); + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/QueryNotFound.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/QueryNotFound.java new file mode 100644 index 0000000000..22379e43a5 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/QueryNotFound.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.exception; + +public class QueryNotFound extends GrafanaAPIException { + + private static final long serialVersionUID = -3151259335930070297L; + public QueryNotFound(String errMsg) { + super(errMsg); + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/TemplateNotFound.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/TemplateNotFound.java new file mode 100644 index 0000000000..69b167a9c2 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/exception/TemplateNotFound.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.exception; + +public class TemplateNotFound extends GrafanaAPIException { + + private static final long serialVersionUID = -3481878481830070297L; + public TemplateNotFound(String errMsg) { + super(errMsg); + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/GrafanaAPIService.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/GrafanaAPIService.java new file mode 100644 index 0000000000..e83bfe4c51 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/GrafanaAPIService.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.service; + +import com.google.gson.JsonObject; +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.bean.Datasource; +import java.io.IOException; + +public interface GrafanaAPIService { + + String getQueryTemplate(String dashboardUID, String panelId, String refId, + String requestScheme) throws IOException, GrafanaManagementException; + + JsonObject getPanelDetails(String dashboardUID, String panelId, String requestScheme) throws + IOException, GrafanaManagementException; + + JsonObject getDashboardDetails(String dashboardUID, String requestScheme) throws IOException, + GrafanaManagementException; + + Datasource getDatasource(int datasourceId, String requestScheme) throws IOException, + GrafanaManagementException; +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/GrafanaQueryService.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/GrafanaQueryService.java new file mode 100644 index 0000000000..ba2c8cbc97 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/GrafanaQueryService.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.service; + +import com.google.gson.JsonObject; +import org.wso2.carbon.device.mgt.common.exceptions.DBConnectionException; +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; + +import java.io.IOException; +import java.net.URI; +import java.sql.SQLException; + +public interface GrafanaQueryService { + + void buildSafeQuery(JsonObject queryRequestBody, String dashboardUID, String panelId, + URI requestUri) throws IOException, SQLException, GrafanaManagementException, DBConnectionException; + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/bean/Datasource.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/bean/Datasource.java new file mode 100644 index 0000000000..4dc84938e4 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/bean/Datasource.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.service.bean; + +public class Datasource { + private int id; + private String url; + private String name; + private String type; + private String database; + + public Datasource(int id, String url, String name, String type, String database) { + this.id = id; + this.url = url; + this.name = name; + this.type = type; + this.database = database; + } + + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/cache/CacheManager.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/cache/CacheManager.java new file mode 100644 index 0000000000..9bcd02d2ed --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/cache/CacheManager.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021, 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 org.wso2.carbon.device.mgt.core.grafana.mgt.service.cache; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; +import org.wso2.carbon.device.mgt.core.grafana.mgt.config.GrafanaConfiguration; +import org.wso2.carbon.device.mgt.core.grafana.mgt.config.GrafanaConfigurationManager; +import org.wso2.carbon.device.mgt.core.grafana.mgt.config.xml.bean.CacheConfiguration; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.bean.Datasource; +import org.wso2.carbon.device.mgt.core.grafana.mgt.util.GrafanaConstants; + +public class CacheManager { + private static final Log log = LogFactory.getLog(CacheManager.class); + + private Cache datasourceAPICache; + private Cache queryTemplateAPICache; + private Cache encodedQueryCache; + + private CacheManager() { + initCache(); + } + + private static final class CacheManagerHolder { + static final CacheManager cacheManager = new CacheManager(); + } + + public static CacheManager getInstance() { + return CacheManagerHolder.cacheManager; + } + + public Cache getEncodedQueryCache() { + return encodedQueryCache; + } + + public Cache getQueryTemplateAPICache() { + return queryTemplateAPICache; + } + + + public Cache getDatasourceAPICache() { + return datasourceAPICache; + } + + + private void initCache() { + this.datasourceAPICache = buildDatasourceCache(); + this.queryTemplateAPICache = buildQueryCacheByName(GrafanaConstants.QUERY_API_CACHE_NAME); + this.encodedQueryCache = buildQueryCacheByName(GrafanaConstants.ENCODED_QUERY_CACHE_NAME); + } + + private Cache buildDatasourceCache() { + return CacheBuilder.newBuilder().build(); + } + + private Cache buildQueryCacheByName(String cacheName) { + int capacity = getCacheCapacity(cacheName); + return CacheBuilder.newBuilder().maximumSize(capacity).build(); + } + + + + private static int getCacheCapacity(String cacheName) { + try { + GrafanaConfiguration configuration = GrafanaConfigurationManager.getInstance().getGrafanaConfiguration(); + CacheConfiguration cacheConfig = configuration.getCacheByName(cacheName); + if (cacheConfig == null) { + log.error("CacheConfiguration config not defined for " + cacheName); + throw new GrafanaManagementException("Query API CacheConfiguration configuration not properly defined"); + } + return cacheConfig.getCapacity(); + } catch (GrafanaManagementException e) { + String errMsg = "Error occurred while initializing cache capacity for " + cacheName; + log.error(errMsg); + throw new RuntimeException(errMsg, e); + } + } + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/cache/QueryTemplateCacheKey.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/cache/QueryTemplateCacheKey.java new file mode 100644 index 0000000000..262be1ead9 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/cache/QueryTemplateCacheKey.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021, 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 org.wso2.carbon.device.mgt.core.grafana.mgt.service.cache; + +import java.util.Objects; + +public class QueryTemplateCacheKey { + + private final String dashboardUID; + private final String panelId; + private final String refId; + private volatile int hashCode; + + public QueryTemplateCacheKey(String dashboardUID, String panelId, String refId) { + this.dashboardUID = dashboardUID; + this.panelId = panelId; + this.refId = refId; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj instanceof QueryTemplateCacheKey) { + final QueryTemplateCacheKey other = (QueryTemplateCacheKey) obj; + String thisId = this.dashboardUID + this.panelId + this.refId; + String otherId = other.dashboardUID + other.panelId + other.refId; + return thisId.equals(otherId); + } + return false; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + hashCode = Objects.hash(dashboardUID, panelId, refId); + } + return hashCode; + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/impl/GrafanaAPIServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/impl/GrafanaAPIServiceImpl.java new file mode 100644 index 0000000000..36e900d003 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/impl/GrafanaAPIServiceImpl.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.service.impl; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; +import org.wso2.carbon.device.mgt.core.common.util.HttpUtil; +import org.wso2.carbon.device.mgt.core.grafana.mgt.exception.DashboardNotFound; +import org.wso2.carbon.device.mgt.core.grafana.mgt.exception.DatasourceNotFound; +import org.wso2.carbon.device.mgt.core.grafana.mgt.exception.GrafanaEnvVariablesNotDefined; +import org.wso2.carbon.device.mgt.core.grafana.mgt.exception.PanelNotFound; +import org.wso2.carbon.device.mgt.core.grafana.mgt.exception.QueryNotFound; +import org.wso2.carbon.device.mgt.core.grafana.mgt.exception.TemplateNotFound; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.GrafanaAPIService; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.bean.Datasource; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.cache.CacheManager; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.cache.QueryTemplateCacheKey; +import org.wso2.carbon.device.mgt.core.grafana.mgt.util.GrafanaConstants; +import org.wso2.carbon.device.mgt.core.grafana.mgt.util.GrafanaUtil; +import org.wso2.carbon.device.mgt.core.report.mgt.Constants; +import javax.ws.rs.core.HttpHeaders; +import java.io.IOException; + +public class GrafanaAPIServiceImpl implements GrafanaAPIService { + + private static final Log log = LogFactory.getLog(GrafanaAPIServiceImpl.class); + + public String getQueryTemplate(String dashboardUID, String panelId, String refId, + String requestScheme) throws IOException, GrafanaManagementException { + try { + return getPanelQuery(dashboardUID, panelId, refId, requestScheme); + } catch (QueryNotFound e) { + return getTemplateQuery(dashboardUID, refId, requestScheme); + } + } + + public String getPanelQuery(String dashboardUID, String panelId, String refId, + String requestScheme) throws IOException, GrafanaManagementException { + JsonObject panel = getPanelDetails(dashboardUID, panelId, requestScheme); + JsonArray queries = panel.getAsJsonArray(GrafanaConstants.DASHBOARD_PANEL_DETAIL_QUERIES_KEY); + JsonObject query = getQueryByRefId(queries, refId); + if (query == null) { + throw new QueryNotFound("No query exists for {dashboard: " + dashboardUID + + ", panelId: " + panelId + ", refId: " + refId + "}"); + } + String queryTemplate = query.get(GrafanaConstants.RAW_SQL_KEY).getAsString(); + CacheManager.getInstance().getQueryTemplateAPICache(). + put(new QueryTemplateCacheKey(dashboardUID, panelId, refId), queryTemplate); + return queryTemplate; + } + + // Here template query means the grafana template variable queries + public String getTemplateQuery(String dashboardUID, String refId, + String requestScheme) throws IOException, GrafanaManagementException { + JsonObject template = getTemplatingDetails(dashboardUID, refId, requestScheme); + JsonElement queryElement = template.get(GrafanaConstants.TEMPLATE_QUERY_KEY); + if (queryElement == null) { + throw new QueryNotFound("No template query exists for {dashboard: " + dashboardUID + + ", refId: " + refId + "}"); + } + String query = queryElement.getAsString(); + CacheManager.getInstance().getQueryTemplateAPICache(). + put(new QueryTemplateCacheKey(dashboardUID, null, refId), query); + return query; + } + + public JsonObject getTemplatingDetails(String dashboardUID, String refId, String requestScheme) throws + IOException, GrafanaManagementException { + JsonObject dashboard = getDashboardDetails(dashboardUID, requestScheme); + JsonObject template = getTemplateByRefId(dashboard, refId); + if (template == null) { + String errorMsg = "Template for {refId: " + refId + ", dashboard: " + dashboardUID + "} is not found"; + log.error(errorMsg); + throw new TemplateNotFound(errorMsg); + } + return template; + } + + public JsonObject getPanelDetails(String dashboardUID, String panelId, String requestScheme) throws + IOException, GrafanaManagementException { + JsonObject dashboard = getDashboardDetails(dashboardUID, requestScheme); + JsonObject panel = getPanelById(dashboard, panelId); + if (panel == null) { + String errorMsg = "Panel " + panelId + " for dashboard " + dashboardUID + " is not found"; + log.error(errorMsg); + throw new PanelNotFound(errorMsg); + } + return panel; + } + + public JsonObject getDashboardDetails(String dashboardUID, String requestScheme) throws IOException, + GrafanaManagementException { + String dashboardAPI = generateGrafanaAPIBaseUri(GrafanaConstants.DASHBOARD_API, requestScheme) + dashboardUID; + HttpGet request = new HttpGet(dashboardAPI); + JsonObject dashboardResponseJsonBody = getGrafanaAPIJsonResponse(request); + JsonObject dashboardDetails = dashboardResponseJsonBody.getAsJsonObject(GrafanaConstants.DASHBOARD_KEY); + if (dashboardDetails == null) { + throw new DashboardNotFound("Grafana response: " + dashboardResponseJsonBody); + } + return dashboardResponseJsonBody.getAsJsonObject(GrafanaConstants.DASHBOARD_KEY); + } + + public Datasource getDatasource(int datasourceId, String requestScheme) throws IOException, GrafanaManagementException { + String datasourceAPI = generateGrafanaAPIBaseUri(GrafanaConstants.DATASOURCE_API, requestScheme) + datasourceId; + HttpGet request = new HttpGet(datasourceAPI); + JsonObject datasourceDetails = getGrafanaAPIJsonResponse(request); + if (datasourceDetails.get(GrafanaConstants.DATASOURCE_NAME_KEY) == null) { + throw new DatasourceNotFound("Grafana response: " + datasourceDetails); + } + String url = datasourceDetails.get(GrafanaConstants.DATASOURCE_URL_KEY).getAsString(); + String name = datasourceDetails.get(GrafanaConstants.DATASOURCE_NAME_KEY).getAsString(); + String type = datasourceDetails.get(GrafanaConstants.DATASOURCE_TYPE_KEY).getAsString(); + String database = datasourceDetails.get(GrafanaConstants.DATASOURCE_DB_KEY).getAsString(); + Datasource ds = new Datasource(datasourceId, url, name, type, database); + CacheManager.getInstance().getDatasourceAPICache().put(datasourceId, ds); + return ds; + } + + private String generateGrafanaAPIBaseUri(String api, String requestScheme) throws GrafanaEnvVariablesNotDefined { + return GrafanaUtil.getGrafanaHTTPBase(requestScheme) + api + Constants.URI_SEPARATOR; + } + + + private JsonObject getGrafanaAPIJsonResponse(HttpRequestBase request) throws IOException, + GrafanaManagementException { + try(CloseableHttpClient client = HttpClients.createDefault()) { + HttpResponse response = invokeAPI(client, request); + return GrafanaUtil.getJsonBody(HttpUtil.getResponseString(response)); + } + } + + private HttpResponse invokeAPI(HttpClient client, HttpRequestBase request) throws IOException, GrafanaManagementException { + setBasicAuthHeader(request); + return client.execute(request); + } + + private void setBasicAuthHeader(HttpRequestBase request) throws GrafanaManagementException { + String basicAuth = GrafanaUtil.getBasicAuthBase64Header(); + request.setHeader(HttpHeaders.AUTHORIZATION, basicAuth); + } + + private JsonObject getQueryByRefId(JsonArray queries, String refId) { + for (int i = 0; i < queries.size(); i++) { + JsonObject query = queries.get(i).getAsJsonObject(); + String queryRefId = query.get(GrafanaConstants.QUERY_REF_ID_KEY).getAsString(); + if (queryRefId.equals(refId)) { + return query; + } + } + return null; + } + + private JsonObject getTemplateByRefId(JsonObject dashboard, String refId) { + JsonArray templates = dashboard.getAsJsonObject(GrafanaConstants.TEMPLATING_KEY). + getAsJsonArray(GrafanaConstants.TEMPLATING_LIST_KEY); + for (int i = 0; i < templates.size(); i++) { + JsonObject templateObj = templates.get(i).getAsJsonObject(); + String name = templateObj.get(GrafanaConstants.TEMPLATING_NAME_KEY).getAsString(); + // RefId in query body corresponds to template name + if (refId.equals(name)) { + return templateObj; + } + } + return null; + } + + private JsonObject getPanelById(JsonObject dashboard, String panelId) { + JsonArray panels = dashboard.getAsJsonArray(GrafanaConstants.PANEL_KEY); + for (int i = 0; i < panels.size(); i++) { + JsonObject panelObj = panels.get(i).getAsJsonObject(); + String id = panelObj.get(GrafanaConstants.ID_KEY).getAsString(); + if (id.equals(panelId)) { + return panelObj; + } + } + return null; + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/impl/GrafanaQueryServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/impl/GrafanaQueryServiceImpl.java new file mode 100644 index 0000000000..915f84eca4 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/service/impl/GrafanaQueryServiceImpl.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.service.impl; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.exceptions.DBConnectionException; +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; +import org.wso2.carbon.device.mgt.core.grafana.mgt.exception.MaliciousQueryAttempt; +import org.wso2.carbon.device.mgt.core.grafana.mgt.exception.QueryMisMatch; +import org.wso2.carbon.device.mgt.core.grafana.mgt.exception.QueryNotFound; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.GrafanaAPIService; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.GrafanaQueryService; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.bean.Datasource; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.cache.CacheManager; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.cache.QueryTemplateCacheKey; +import org.wso2.carbon.device.mgt.core.grafana.mgt.sql.query.GrafanaPreparedQueryBuilder; +import org.wso2.carbon.device.mgt.core.grafana.mgt.sql.query.PreparedQuery; +import org.wso2.carbon.device.mgt.core.grafana.mgt.sql.query.encoder.QueryEncoderFactory; +import org.wso2.carbon.device.mgt.core.grafana.mgt.util.GrafanaConstants; +import java.io.IOException; +import java.net.URI; +import java.sql.SQLException; + +public class GrafanaQueryServiceImpl implements GrafanaQueryService { + + private static final Log log = LogFactory.getLog(GrafanaQueryServiceImpl.class); + private final GrafanaAPIService grafanaAPIService; + + public GrafanaQueryServiceImpl(GrafanaAPIService grafanaAPIService) { + this.grafanaAPIService = grafanaAPIService; + } + + public void buildSafeQuery(JsonObject queryRequestBody, String dashboardUID, String panelId, URI requestUri) + throws IOException, SQLException, GrafanaManagementException, DBConnectionException { + JsonArray queries = queryRequestBody.getAsJsonArray(GrafanaConstants.QUERY_BODY_QUERIES_KEY); + for (int i = 0; i < queries.size(); i++) { + JsonObject queryObj = queries.get(i).getAsJsonObject(); + JsonElement refIdJson = queryObj.get(GrafanaConstants.QUERY_REF_ID_KEY); + JsonElement rawSqlJson = queryObj.get(GrafanaConstants.RAW_SQL_KEY); + JsonElement datasourceIdJson = queryObj.get(GrafanaConstants.DATASOURCE_ID_KEY); + if (refIdJson == null || rawSqlJson == null || datasourceIdJson == null) { + String errMsg = "Query json body: refId, rawSql and datasourceId cannot be null"; + log.error(errMsg); + throw new MaliciousQueryAttempt(errMsg); + } + String refId = refIdJson.getAsString(); + String rawSql = rawSqlJson.getAsString(); + int datasourceId = datasourceIdJson.getAsInt(); + CacheManager cacheManager = CacheManager.getInstance(); + String encodedQuery = cacheManager.getEncodedQueryCache().getIfPresent(rawSql); + if (cacheManager.getEncodedQueryCache().getIfPresent(rawSql) != null) { + queryObj.addProperty(GrafanaConstants.RAW_SQL_KEY, encodedQuery); + return; + } + Datasource datasource = cacheManager.getDatasourceAPICache().getIfPresent(datasourceId); + if (datasource == null) { + datasource = grafanaAPIService.getDatasource(datasourceId, requestUri.getScheme()); + } + String queryTemplate = cacheManager.getQueryTemplateAPICache(). + getIfPresent(new QueryTemplateCacheKey(dashboardUID, panelId, refId)); + try { + if (queryTemplate != null) { + try { + encodeQuery(queryObj, datasource, queryTemplate, rawSql); + } catch (QueryMisMatch e) { + log.error("Error occurred while encoding query, " + + "retrying to encode by getting the query template from api instead of cache", e); + queryTemplate = grafanaAPIService.getQueryTemplate(dashboardUID, panelId, refId, requestUri.getScheme()); + encodeQuery(queryObj, datasource, queryTemplate, rawSql); + } + } else { + queryTemplate = grafanaAPIService.getQueryTemplate(dashboardUID, panelId, refId, requestUri.getScheme()); + encodeQuery(queryObj, datasource, queryTemplate, rawSql); + } + } catch (QueryNotFound e) { + String errMsg = "No query exists for {dashboard: " + dashboardUID + + ", panelId: " + panelId + ", refId: " + refId + "}"; + log.error(errMsg); + throw new QueryNotFound(errMsg); + } + } + } + + private void encodeQuery(JsonObject queryObj, Datasource datasource, String queryTemplate, String rawSql) + throws SQLException, GrafanaManagementException, DBConnectionException { + PreparedQuery pq = GrafanaPreparedQueryBuilder.build(queryTemplate, rawSql); + String encodedQuery = QueryEncoderFactory.createEncoder(datasource.getType(), datasource.getName()).encode(pq); + CacheManager.getInstance().getEncodedQueryCache().put(rawSql, encodedQuery); + if(log.isDebugEnabled()) { + log.debug("Encoded query: " + encodedQuery); + } + queryObj.addProperty(GrafanaConstants.RAW_SQL_KEY, encodedQuery); + } + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/connection/GrafanaDatasourceConnectionFactory.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/connection/GrafanaDatasourceConnectionFactory.java new file mode 100644 index 0000000000..b9ffbd39ce --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/connection/GrafanaDatasourceConnectionFactory.java @@ -0,0 +1,55 @@ +package org.wso2.carbon.device.mgt.core.grafana.mgt.sql.connection; + +import org.wso2.carbon.device.mgt.common.exceptions.DBConnectionException; +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; +import org.wso2.carbon.device.mgt.core.config.DeviceConfigurationManager; +import org.wso2.carbon.device.mgt.core.config.DeviceManagementConfig; +import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOFactory; +import org.wso2.carbon.device.mgt.core.grafana.mgt.config.GrafanaConfiguration; +import org.wso2.carbon.device.mgt.core.grafana.mgt.config.GrafanaConfigurationManager; +import org.wso2.carbon.device.mgt.core.report.mgt.config.ReportMgtConfiguration; +import org.wso2.carbon.device.mgt.core.report.mgt.config.ReportMgtConfigurationManager; +import org.wso2.carbon.device.mgt.core.report.mgt.dao.common.ReportMgtConnectionManager; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; + +public class GrafanaDatasourceConnectionFactory { + + private static final ReportMgtConfiguration reportMgtConfiguration= ReportMgtConfigurationManager.getInstance(). + getConfiguration(); + private static final DeviceManagementConfig deviceManagementConfig = DeviceConfigurationManager.getInstance(). + getDeviceManagementConfig(); + + public static Connection getConnection(String databaseName) throws SQLException, DBConnectionException { + if(databaseName.equals(getReportManagementDatasourceName())) { + ReportMgtConnectionManager.openDBConnection(); + return ReportMgtConnectionManager.getDBConnection(); + } else if (databaseName.equals(getDeviceManagementDatasourceName())) { + DeviceManagementDAOFactory.openConnection(); + return DeviceManagementDAOFactory.getConnection(); + } else { + throw new RuntimeException("No such datasource with the name: " + databaseName); + } + } + public static void closeConnection(String databaseName) throws SQLException, DBConnectionException { + if(databaseName.equals(getReportManagementDatasourceName())) { + ReportMgtConnectionManager.closeDBConnection(); + } else if (databaseName.equals(getDeviceManagementDatasourceName())) { + DeviceManagementDAOFactory.closeConnection(); + } else { + throw new RuntimeException("No such datasource with the name: " + databaseName); + } + } + + private static String getReportManagementDatasourceName() { + return reportMgtConfiguration.getDatasourceName(); + } + + private static String getDeviceManagementDatasourceName() { + return deviceManagementConfig.getDeviceManagementConfigRepository().getDataSourceConfig(). + getJndiLookupDefinition().getJndiName(); + } + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/GrafanaPreparedQueryBuilder.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/GrafanaPreparedQueryBuilder.java new file mode 100644 index 0000000000..d938a2b320 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/GrafanaPreparedQueryBuilder.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.sql.query; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.core.grafana.mgt.exception.QueryMisMatch; +import org.wso2.carbon.device.mgt.core.grafana.mgt.util.GrafanaConstants; +import org.wso2.carbon.device.mgt.core.grafana.mgt.util.GrafanaUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class GrafanaPreparedQueryBuilder { + + private static final Log log = LogFactory.getLog(GrafanaPreparedQueryBuilder.class); + + private static final String QUERY_MISMATCH_EXCEPTION_MESSAGE = "Grafana query api request rawSql does not match relevant query template"; + private static final String COMMA_SEPARATOR = ","; + private static final String VAR_PARAM_TEMPLATE = "$param"; + private static final String GRAFANA_QUOTED_VAR_REGEX = "('\\$(\\d|\\w|_)+')|('\\$\\{.*?\\}')|(\"\\$(\\d|\\w|_)+\")|(\"\\$\\{.*?\\}\")"; + private static final String GRAFANA_VAR_REGEX = "(\\$(\\d|\\w|_)+)|(\\$\\{.*?\\})"; + + + public static PreparedQuery build(String queryTemplate, String rawQuery) throws QueryMisMatch { + // In Grafana, a variable can coexist with a hardcoded parameter (ie: '${__from:date:YYYY-MM-DD} 00:00:00') + // hence this function templates the whole parameter in order to make valid prepared statement + queryTemplate = templateParamWGrafanaVariables(queryTemplate); + + String queryUpToVarStart = queryTemplate; + String rawQueryUpToVarStart = rawQuery; + + // Pattern matcher to get query string before the first grafana variable + Pattern toVarStart = Pattern.compile("([^\\$]+(?=\"\\$))|([^\\$]+(?='\\$))|([^\\$]+(?=\\$))"); + Matcher matchToVarStart = toVarStart.matcher(queryTemplate); + + if (matchToVarStart.find()) { + queryUpToVarStart = matchToVarStart.group(); + if(rawQuery.length() < queryUpToVarStart.length()) { + throw new QueryMisMatch(QUERY_MISMATCH_EXCEPTION_MESSAGE); + } + rawQueryUpToVarStart = rawQuery.substring(0, queryUpToVarStart.length()); + } + if(!queryUpToVarStart.equals(rawQueryUpToVarStart)){ + throw new QueryMisMatch(QUERY_MISMATCH_EXCEPTION_MESSAGE); + } + StringBuilder preparedQueryBuilder = new StringBuilder().append(queryUpToVarStart); + + String queryFromVarStart = queryTemplate.substring(queryUpToVarStart.length()); + String rawQueryFromVarStart = rawQuery.substring(queryUpToVarStart.length()); + queryTemplate = queryFromVarStart; + rawQuery = rawQueryFromVarStart; + Pattern varPattern = Pattern.compile(GRAFANA_QUOTED_VAR_REGEX + "|" + GRAFANA_VAR_REGEX); + Matcher varMatch = varPattern.matcher(queryTemplate); + List parameters = new ArrayList<>(); + while(varMatch.find()) { + String currentVar = varMatch.group(); + // Pattern matcher to get query string between grafana current variable and next variable + matchToVarStart = toVarStart.matcher(queryTemplate.substring(currentVar.length())); + + String matchToLookBehindRawQuery; + if (matchToVarStart.find()) { + matchToLookBehindRawQuery = matchToVarStart.group(); + } else { + // If next variable does not exist get query string after the current variable + matchToLookBehindRawQuery = queryTemplate.substring(currentVar.length()); + } + String currentVarInput; + if (matchToLookBehindRawQuery.isEmpty()) { + // If there is no string after the current variable, then remaining segment of rawQuery is the + // current variable input (rawQuery is sliced up to the next variable) + currentVarInput = rawQuery; + } else { + Matcher lookBehindRQ = Pattern.compile("(.*?)(?=" + Pattern.quote(matchToLookBehindRawQuery) + ")").matcher(rawQuery); + if (!lookBehindRQ.find()) { + throw new QueryMisMatch(QUERY_MISMATCH_EXCEPTION_MESSAGE); + } + currentVarInput = lookBehindRQ.group(); + } + if (isTenantIdVar(currentVar)) { + preparedQueryBuilder.append(GrafanaUtil.getTenantId()); + } else { + // Grafana variable input can be multi-valued, which are separated by comma by default + String[] varValues = splitByComma(currentVarInput); + List preparedStatementPlaceHolders = new ArrayList<>(); + for (String v : varValues) { + String param = unQuoteString(v); + if (isSafeVariableInput(param)) { + preparedStatementPlaceHolders.add(v); + } else { + parameters.add(param); + preparedStatementPlaceHolders.add(PreparedQuery.PREPARED_SQL_PARAM_PLACEHOLDER); + } + } + preparedQueryBuilder.append(String.join(COMMA_SEPARATOR, preparedStatementPlaceHolders)); + } + preparedQueryBuilder.append(matchToLookBehindRawQuery); + // Get template and raw query string from next variable + queryTemplate = queryTemplate.substring(currentVar.length() + matchToLookBehindRawQuery.length()); + rawQuery = rawQuery.substring(currentVarInput.length() + matchToLookBehindRawQuery.length()); + + varMatch = varPattern.matcher(queryTemplate); + } + if (!queryTemplate.equals(rawQuery)) { + throw new QueryMisMatch(QUERY_MISMATCH_EXCEPTION_MESSAGE); + } + return new PreparedQuery(preparedQueryBuilder.toString(), parameters); + } + + private static String[] splitByComma(String str) { + // Using regex to avoid splitting by comma inside quotes + return str.split("(\\s|\\t)*,(\\s|\\t)*(?=(?:[^'\"]*['|\"][^'\"]*['|\"])*[^'\"]*$)"); + } + + private static String templateParamWGrafanaVariables(String queryTemplate) { + // TODO: handle escaped quotes and special characters + Pattern quotedStringPattern = Pattern.compile("(\"(.+?)\")|('(.+?)')"); + Matcher quotedStringMatch = quotedStringPattern.matcher(queryTemplate); + while(quotedStringMatch.find()) { + String quotedString = quotedStringMatch.group(); + Matcher varMatcher = Pattern.compile(GRAFANA_VAR_REGEX).matcher(quotedString); + // If grafana variable exists in single quoted string + if(varMatcher.find()) { + String var = varMatcher.group(); + if (!isTenantIdVar(var)) { + String templatedQuotedString = templateQuotedString(quotedString); + // escape any special characters + templatedQuotedString = Matcher.quoteReplacement(templatedQuotedString); + queryTemplate = queryTemplate.replaceFirst(Pattern.quote(quotedString), templatedQuotedString); + } + } + } + return queryTemplate; + } + + private static String templateQuotedString(String quotedString) { + return quotedString.replaceFirst("[^']+|[^\"]+", + Matcher.quoteReplacement(VAR_PARAM_TEMPLATE)); + } + + private static boolean isSafeVariableInput(String currentVarInput) { + if (StringUtils.isEmpty(currentVarInput)) { + return true; + } + return currentVarInput.matches("\\$?[a-zA-Z0-9-_\\.]+|^\"[a-zA-Z0-9-_\\.\\s]+\"$|^'[a-zA-Z0-9-_\\.\\s]+'$"); + } + + private static String unQuoteString(String str) { + if (isQuoted(str)) { + int firstCharIndex = 0; + int lastCharIndex = str.length() - 1; + return str.substring(firstCharIndex + 1, lastCharIndex); + } + return str; + } + + private static boolean isQuoted(String str) { + if (StringUtils.isEmpty(str)) { + return false; + } + int firstCharIndex = 0; + int lastCharIndex = str.length() - 1; + return (str.charAt(firstCharIndex) == '\'' && str.charAt(lastCharIndex) == '\'') || + (str.charAt(firstCharIndex) == '"' && str.charAt(lastCharIndex) == '"'); + } + + private static boolean isTenantIdVar(String var) { + return var.equals(GrafanaConstants.TENANT_ID_VAR) || var.equals(singleQuoteString(GrafanaConstants.TENANT_ID_VAR)) + || var.equals(doubleQuoteString(GrafanaConstants.TENANT_ID_VAR)); + } + + private static String doubleQuoteString(String str) { + return "\"" + str + "\""; + } + + private static String singleQuoteString(String str) { + return "'" + str + "'"; + } + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/PreparedQuery.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/PreparedQuery.java new file mode 100644 index 0000000000..11f240b1af --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/PreparedQuery.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.sql.query; + +import java.util.List; + +public class PreparedQuery { + + public static final String PREPARED_SQL_PARAM_PLACEHOLDER = "?"; + + private String preparedSQL; + private List parameters; + + public PreparedQuery(String preparedSQL, List parameters) { + this.preparedSQL = preparedSQL; + this.parameters = parameters; + } + + public String getPreparedSQL() { + return preparedSQL; + } + + public void setPreparedSQL(String preparedSQL) { + this.preparedSQL = preparedSQL; + } + + public List getParameters() { + return parameters; + } + + public void setParameters(List parameters) { + this.parameters = parameters; + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/encoder/GenericQueryEncoder.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/encoder/GenericQueryEncoder.java new file mode 100644 index 0000000000..031b8bd123 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/encoder/GenericQueryEncoder.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.sql.query.encoder; + +import org.apache.commons.lang.StringEscapeUtils; +import org.wso2.carbon.device.mgt.core.grafana.mgt.sql.query.PreparedQuery; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class GenericQueryEncoder implements QueryEncoder{ + + public GenericQueryEncoder() { + } + + @Override + public String encode(PreparedQuery preparedQuery) throws SQLException { + return generateEncodedSQL(preparedQuery.getPreparedSQL(), preparedQuery.getParameters()); + } + + private String generateEncodedSQL(String preparedSQL, List parameters) throws SQLException { + List escapedParams = escapeParameters(parameters); + Matcher placeHolderMatcher = Pattern.compile(Pattern.quote(PreparedQuery.PREPARED_SQL_PARAM_PLACEHOLDER)).matcher(preparedSQL); + StringBuffer sb = new StringBuffer(); + for (String param: escapedParams) { + if(placeHolderMatcher.find()) { + placeHolderMatcher.appendReplacement(sb, Matcher.quoteReplacement(param)); + } else { + String errMsg = "Given parameter count doesn't match parameters available in SQL"; + throw new SQLException(errMsg); + } + } + return sb.toString(); + } + + private List escapeParameters(List parameters) { + List escapedParams = new ArrayList<>(); + for (String param : parameters) { + String escapedParam = StringEscapeUtils.escapeSql(param); + escapedParams.add(escapedParam); + } + return escapedParams; + } + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/encoder/MySQLQueryEncoder.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/encoder/MySQLQueryEncoder.java new file mode 100644 index 0000000000..0c1220217e --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/encoder/MySQLQueryEncoder.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.sql.query.encoder; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.exceptions.DBConnectionException; +import org.wso2.carbon.device.mgt.core.grafana.mgt.sql.connection.GrafanaDatasourceConnectionFactory; +import org.wso2.carbon.device.mgt.core.grafana.mgt.sql.query.PreparedQuery; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; + +public class MySQLQueryEncoder implements QueryEncoder { + + private static final Log log = LogFactory.getLog(MySQLQueryEncoder.class); + private static final String PREPARED_STATEMENT_STRING_OBJECT_ID_SEPARATOR = ": "; + + private final String databaseName; + + public MySQLQueryEncoder(String databaseName) { + this.databaseName = databaseName; + } + + @Override + public String encode(PreparedQuery preparedQuery) throws SQLException, DBConnectionException { + try { + Connection con = GrafanaDatasourceConnectionFactory.getConnection(databaseName); + PreparedStatement stmt = con.prepareStatement(preparedQuery.getPreparedSQL()); + setParameters(stmt, preparedQuery.getParameters()); + return generateQueryFromPreparedStatement(stmt); + } finally { + GrafanaDatasourceConnectionFactory.closeConnection(databaseName); + } + } + + public void setParameters(PreparedStatement stmt, List parameters) + throws SQLException { + int i = 0; + for (String p : parameters) { + stmt.setObject(++i, p); + } + } + + private String generateQueryFromPreparedStatement(PreparedStatement stmt) { + String query = stmt.toString().substring(stmt.toString().indexOf(PREPARED_STATEMENT_STRING_OBJECT_ID_SEPARATOR) + + PREPARED_STATEMENT_STRING_OBJECT_ID_SEPARATOR.length()); + // remove unnecessary "]" char at the end + if (query.charAt(query.length() - 1) == ']') { + query = query.substring(0, query.length() - 1); + } + return query; + } + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/encoder/QueryEncoder.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/encoder/QueryEncoder.java new file mode 100644 index 0000000000..724557d434 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/encoder/QueryEncoder.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.sql.query.encoder; + +import org.wso2.carbon.device.mgt.common.exceptions.DBConnectionException; +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; +import org.wso2.carbon.device.mgt.core.grafana.mgt.sql.query.PreparedQuery; + +import java.sql.SQLException; + +public interface QueryEncoder { + + String encode(PreparedQuery preparedQuery) throws SQLException, DBConnectionException; + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/encoder/QueryEncoderFactory.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/encoder/QueryEncoderFactory.java new file mode 100644 index 0000000000..383f9a61b2 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/sql/query/encoder/QueryEncoderFactory.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.sql.query.encoder; + +import org.wso2.carbon.device.mgt.core.grafana.mgt.util.GrafanaConstants; + +public class QueryEncoderFactory { + + public static QueryEncoder createEncoder(String datasourceType, String databaseName) { + if (datasourceType.equals(GrafanaConstants.DATASOURCE_TYPE.MYSQL)) { + return new MySQLQueryEncoder(databaseName); + } else { + return new GenericQueryEncoder(); + } + + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/util/GrafanaConstants.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/util/GrafanaConstants.java new file mode 100644 index 0000000000..0dfe683c2a --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/util/GrafanaConstants.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.wso2.carbon.device.mgt.core.grafana.mgt.util; + +public class GrafanaConstants { + + public static final String QUERY_API_CACHE_NAME = "queryAPICache"; + public static final String ENCODED_QUERY_CACHE_NAME = "encodedQueryCache"; + public static final String REFERER_HEADER = "Referer"; + public static final String CONFIG_XML_NAME = "grafana-config.xml"; + public static final String X_GRAFANA_ORG_ID_HEADER = "x-org-grafana-id"; + + public static class XML { + public static final String FEATURES_DISALLOW_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl"; + } + + public static class DATASOURCE_TYPE { + public static final String MYSQL = "mysql"; + public static final String POSTGRESQL = "postgresql"; + public static final String MICROSOFT_SQL_SERVER = "mssql"; + public static final String ORACLE = "oracle"; + } + + public static final int IFRAME_URL_DASHBOARD_UID_INDEX = 1; + + public static final String TENANT_ID_VAR_NAME = "tenantId"; + public static final String VAR_PREFIX = "$"; + public static final String TENANT_ID_VAR = VAR_PREFIX + TENANT_ID_VAR_NAME; + public static final String QUERY_PARAM_VAR_PREFIX = "var-"; + public static final String TENANT_ID_VAR_QUERY_PARAM = QUERY_PARAM_VAR_PREFIX + TENANT_ID_VAR_NAME; + public static final String API_PATH = "/api"; + public static final String DASHBOARD_KEY = "dashboard"; + public static final String DATASOURCE_TYPE_KEY = "type"; + public static final String DATASOURCE_DB_KEY = "database"; + public static final String DATASOURCE_ID_KEY = "datasourceId"; + public static final String DATASOURCE_NAME_KEY = "name"; + public static final String DATASOURCE_URL_KEY = "name"; + public static final String PANEL_KEY = "panels"; + public static final String TEMPLATING_KEY = "templating"; + public static final String TEMPLATING_NAME_KEY = "name"; + public static final String TEMPLATE_QUERY_KEY = "query"; + public static final String TEMPLATING_LIST_KEY = "list"; + public static final String RAW_SQL_KEY = "rawSql"; + public static final String QUERY_BODY_QUERIES_KEY = "queries"; + public static final String DASHBOARD_PANEL_DETAIL_QUERIES_KEY = "targets"; + public static final String QUERY_REF_ID_KEY = "refId"; + public static final String PANEL_ID_QUERY_PARAM = "panelId"; + public static final String ORG_ID_QUERY_PARAM = "panelId"; + public static final String ID_KEY = "id"; + + public static final String WS_LIVE_API = "/api/live/ws"; + public static final String DASHBOARD_API = "/api/dashboards/uid"; + public static final String DATASOURCE_API = "/api/datasources"; + public static final String HTTP_HOST_ENV_VAR = "iot.grafana.http.host"; + public static final String HTTPS_HOST_ENV_VAR = "iot.grafana.https.host"; +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/util/GrafanaUtil.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/util/GrafanaUtil.java new file mode 100644 index 0000000000..d78cc23ff1 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/grafana/mgt/util/GrafanaUtil.java @@ -0,0 +1,141 @@ +/* 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 org.wso2.carbon.device.mgt.core.grafana.mgt.util; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; +import org.wso2.carbon.device.mgt.core.common.util.HttpUtil; +import org.wso2.carbon.device.mgt.core.grafana.mgt.bean.GrafanaPanelIdentifier; +import org.wso2.carbon.device.mgt.core.grafana.mgt.config.GrafanaConfiguration; +import org.wso2.carbon.device.mgt.core.grafana.mgt.config.GrafanaConfigurationManager; +import org.wso2.carbon.device.mgt.core.grafana.mgt.exception.GrafanaEnvVariablesNotDefined; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.GrafanaAPIService; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.GrafanaQueryService; +import org.wso2.carbon.device.mgt.core.report.mgt.Constants; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +public class GrafanaUtil { + + private static final Log log = LogFactory.getLog(GrafanaUtil.class); + + public static boolean isGrafanaAPI(String uri) { + return uri.contains(GrafanaConstants.API_PATH); + } + + public static String generateGrafanaUrl(String requestPath, String base) { + base += Constants.URI_SEPARATOR; + return base + requestPath; + } + public static String getGrafanaWebSocketBase(String requestScheme) throws GrafanaEnvVariablesNotDefined { + return getGrafanaBase(requestScheme, Constants.WS_PROTOCOL, Constants.WSS_PROTOCOL); + } + public static String getGrafanaHTTPBase(String requestScheme) throws GrafanaEnvVariablesNotDefined { + return getGrafanaBase(requestScheme, Constants.HTTP_PROTOCOL, Constants.HTTPS_PROTOCOL); + } + + public static String getGrafanaBase(String requestScheme, String httpProtocol, String httpsProtocol) + throws GrafanaEnvVariablesNotDefined { + String grafanaHost = System.getProperty(GrafanaConstants.HTTPS_HOST_ENV_VAR); + String scheme = httpsProtocol; + if (Constants.HTTP_PROTOCOL.equals(requestScheme) || grafanaHost == null){ + grafanaHost = System.getProperty(GrafanaConstants.HTTP_HOST_ENV_VAR); + scheme = httpProtocol; + } + if(grafanaHost == null) { + String errMsg = "Grafana host is not defined in the iot-server.sh properly."; + log.error(errMsg); + throw new GrafanaEnvVariablesNotDefined(errMsg); + } + return scheme + Constants.SCHEME_SEPARATOR + grafanaHost; + } + + public static String getPanelId(URI iframeURL) { + Map> queryMap = HttpUtil.getQueryMap(iframeURL); + return HttpUtil.getFirstQueryValue(GrafanaConstants.PANEL_ID_QUERY_PARAM, queryMap); + } + public static String getOrgId(URI iframeURL) { + Map> queryMap = HttpUtil.getQueryMap(iframeURL); + return HttpUtil.getFirstQueryValue(GrafanaConstants.ORG_ID_QUERY_PARAM, queryMap); + } + + public static String getDashboardUID(URI iframeURL) { + return HttpUtil.getRequestSubPathFromEnd(iframeURL, GrafanaConstants.IFRAME_URL_DASHBOARD_UID_INDEX); + } + + public static JsonObject getJsonBody(String body) { + return new Gson().fromJson(body, JsonObject.class); + } + + public static String getBasicAuthBase64Header() throws GrafanaManagementException { + GrafanaConfiguration configuration = GrafanaConfigurationManager.getInstance().getGrafanaConfiguration(); + String username = configuration.getAdminUser().getUsername(); + String password = configuration.getAdminUser().getPassword(); + return Constants.BASIC_AUTH_HEADER_PREFIX + GrafanaUtil.getBase64Encode(username, password); + } + + public static String getBase64Encode(String key, String value) { + return new String(Base64.encodeBase64((key + ":" + value).getBytes())); + } + + public static GrafanaPanelIdentifier getPanelIdentifierFromReferer(String referer) { + URI refererUri = HttpUtil.createURI(referer); + String orgId = GrafanaUtil.getOrgId(refererUri); + String dashboardUID = GrafanaUtil.getDashboardUID(refererUri); + String panelId = GrafanaUtil.getPanelId(refererUri); + return new GrafanaPanelIdentifier(orgId, dashboardUID, panelId); + } + + public static int getTenantId() { + return PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true); + } + + public static GrafanaAPIService getGrafanaAPIService() { + GrafanaAPIService grafanaAPIService; + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + grafanaAPIService = (GrafanaAPIService) ctx.getOSGiService( + GrafanaAPIService.class, null); + if (grafanaAPIService == null) { + String msg = "Report Management service not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return grafanaAPIService; + } + + public static GrafanaQueryService getGrafanaQueryService() { + GrafanaQueryService grafanaQueryService; + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + grafanaQueryService = (GrafanaQueryService) ctx.getOSGiService( + GrafanaQueryService.class, null); + if (grafanaQueryService == null) { + String msg = "Report Management service not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return grafanaQueryService; + } + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/internal/DeviceManagementServiceComponent.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/internal/DeviceManagementServiceComponent.java index 859659bdf5..bfaf80a284 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/internal/DeviceManagementServiceComponent.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/internal/DeviceManagementServiceComponent.java @@ -56,6 +56,10 @@ import org.wso2.carbon.device.mgt.core.device.details.mgt.DeviceInformationManag import org.wso2.carbon.device.mgt.core.device.details.mgt.impl.DeviceInformationManagerImpl; import org.wso2.carbon.device.mgt.core.event.config.EventConfigurationProviderServiceImpl; import org.wso2.carbon.device.mgt.core.geo.service.GeoLocationProviderServiceImpl; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.GrafanaAPIService; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.GrafanaQueryService; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.impl.GrafanaAPIServiceImpl; +import org.wso2.carbon.device.mgt.core.grafana.mgt.service.impl.GrafanaQueryServiceImpl; import org.wso2.carbon.device.mgt.core.metadata.mgt.MetadataManagementServiceImpl; import org.wso2.carbon.device.mgt.core.metadata.mgt.dao.MetadataManagementDAOFactory; import org.wso2.carbon.device.mgt.core.notification.mgt.NotificationManagementServiceImpl; @@ -330,6 +334,13 @@ public class DeviceManagementServiceComponent { = new NotificationManagementServiceImpl(); bundleContext.registerService(NotificationManagementService.class.getName(), notificationManagementService, null); + /* Registering Grafana Services */ + GrafanaAPIService grafanaAPIService = new GrafanaAPIServiceImpl(); + GrafanaQueryService grafanaQueryService = new GrafanaQueryServiceImpl(grafanaAPIService); + bundleContext.registerService(GrafanaAPIService.class.getName(), grafanaAPIService, null); + bundleContext.registerService(GrafanaQueryService.class.getName(), grafanaQueryService, null); + + /* Registering Report Service */ ReportManagementService reportManagementService = new ReportManagementServiceImpl(); bundleContext.registerService(ReportManagementService.class.getName(), reportManagementService, null); diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/operation/mgt/dao/impl/GenericOperationDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/operation/mgt/dao/impl/GenericOperationDAOImpl.java index 55fb0d16ae..3723bb488f 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/operation/mgt/dao/impl/GenericOperationDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/operation/mgt/dao/impl/GenericOperationDAOImpl.java @@ -1173,10 +1173,10 @@ public class GenericOperationDAOImpl implements OperationDAO { Operation operation = null; try { Connection conn = OperationManagementDAOFactory.getConnection(); - String sql = "SELECT o.ID, o.TYPE, o.CREATED_TIMESTAMP, o.RECEIVED_TIMESTAMP, om.STATUS, o.OPERATION_CODE, " + - "om.ID AS OM_MAPPING_ID, " + + String sql = "SELECT o.ID, o.TYPE, o.CREATED_TIMESTAMP, o.RECEIVED_TIMESTAMP, om.STATUS, " + + "o.OPERATION_CODE, o.INITIATED_BY, om.ID AS OM_MAPPING_ID, " + "om.UPDATED_TIMESTAMP FROM (SELECT ID, TYPE, CREATED_TIMESTAMP, RECEIVED_TIMESTAMP," + - "OPERATION_CODE FROM DM_OPERATION WHERE id = ?) o INNER JOIN (SELECT * FROM " + + "OPERATION_CODE, INITIATED_BY FROM DM_OPERATION WHERE id = ?) o INNER JOIN (SELECT * FROM " + "DM_ENROLMENT_OP_MAPPING dm where dm.OPERATION_ID = ? AND dm.ENROLMENT_ID = ?) om " + "ON o.ID = om.OPERATION_ID "; stmt = conn.prepareStatement(sql); @@ -1198,6 +1198,7 @@ public class GenericOperationDAOImpl implements OperationDAO { new Timestamp((rs.getLong("UPDATED_TIMESTAMP") * 1000)).toString()); } operation.setCode(rs.getString("OPERATION_CODE")); + operation.setInitiatedBy(rs.getString("INITIATED_BY")); OperationDAOUtil.setActivityId(operation, rs.getInt("ID")); } } catch (SQLException e) { @@ -1215,11 +1216,11 @@ public class GenericOperationDAOImpl implements OperationDAO { PreparedStatement stmt = null; ResultSet rs = null; Operation operation; - List operations = new ArrayList(); + List operations = new ArrayList<>(); try { Connection conn = OperationManagementDAOFactory.getConnection(); - String sql = "SELECT o.ID, TYPE, o.CREATED_TIMESTAMP, o.RECEIVED_TIMESTAMP, o.OPERATION_CODE, om.ID AS OM_MAPPING_ID," + - "om.UPDATED_TIMESTAMP FROM DM_OPERATION o " + + String sql = "SELECT o.ID, TYPE, o.CREATED_TIMESTAMP, o.RECEIVED_TIMESTAMP, o.OPERATION_CODE, " + + "o.INITIATED_BY, om.ID AS OM_MAPPING_ID, om.UPDATED_TIMESTAMP FROM DM_OPERATION o " + "INNER JOIN (SELECT * FROM DM_ENROLMENT_OP_MAPPING dm " + "WHERE dm.ENROLMENT_ID = ? AND dm.STATUS = ?) om ON o.ID = om.OPERATION_ID ORDER BY o.CREATED_TIMESTAMP DESC"; stmt = conn.prepareStatement(sql); @@ -1239,6 +1240,7 @@ public class GenericOperationDAOImpl implements OperationDAO { new Timestamp((rs.getLong("UPDATED_TIMESTAMP") * 1000)).toString()); } operation.setCode(rs.getString("OPERATION_CODE")); + operation.setInitiatedBy(rs.getString("INITIATED_BY")); operation.setStatus(status); OperationDAOUtil.setActivityId(operation, rs.getInt("ID")); operations.add(operation); @@ -1263,7 +1265,7 @@ public class GenericOperationDAOImpl implements OperationDAO { try { Connection conn = OperationManagementDAOFactory.getConnection(); String sql = "SELECT o.ID, TYPE, o.CREATED_TIMESTAMP, o.RECEIVED_TIMESTAMP, o.OPERATION_CODE, " + - "om.ID AS OM_MAPPING_ID, om.UPDATED_TIMESTAMP FROM DM_OPERATION o " + + "o.INITIATED_BY, om.ID AS OM_MAPPING_ID, om.UPDATED_TIMESTAMP FROM DM_OPERATION o " + "INNER JOIN (SELECT * FROM DM_ENROLMENT_OP_MAPPING dm " + "WHERE dm.ENROLMENT_ID = ? AND dm.STATUS = ?) om ON o.ID = om.OPERATION_ID ORDER BY " + "o.CREATED_TIMESTAMP DESC LIMIT ?,?"; @@ -1286,6 +1288,7 @@ public class GenericOperationDAOImpl implements OperationDAO { new Timestamp((rs.getLong("UPDATED_TIMESTAMP") * 1000)).toString()); } operation.setCode(rs.getString("OPERATION_CODE")); + operation.setInitiatedBy(rs.getString("INITIATED_BY")); operation.setStatus(status); OperationDAOUtil.setActivityId(operation, rs.getInt("OM_MAPPING_ID")); operations.add(operation); @@ -1309,8 +1312,8 @@ public class GenericOperationDAOImpl implements OperationDAO { try { Connection conn = OperationManagementDAOFactory.getConnection(); String sql = "SELECT o.ID, o.TYPE, o.CREATED_TIMESTAMP, o.RECEIVED_TIMESTAMP, " + - "o.OPERATION_CODE, om.STATUS, om.ID AS OM_MAPPING_ID, om.UPDATED_TIMESTAMP FROM DM_OPERATION o " + - "INNER JOIN (SELECT * FROM DM_ENROLMENT_OP_MAPPING dm " + + "o.OPERATION_CODE, o.INITIATED_BY, om.STATUS, om.ID AS OM_MAPPING_ID, om.UPDATED_TIMESTAMP " + + "FROM DM_OPERATION o INNER JOIN (SELECT * FROM DM_ENROLMENT_OP_MAPPING dm " + "WHERE dm.ENROLMENT_ID = ?) om ON o.ID = om.OPERATION_ID " + "ORDER BY o.CREATED_TIMESTAMP DESC, o.ID DESC"; stmt = conn.prepareStatement(sql); @@ -1329,6 +1332,7 @@ public class GenericOperationDAOImpl implements OperationDAO { new Timestamp((rs.getLong("UPDATED_TIMESTAMP") * 1000)).toString()); } operation.setCode(rs.getString("OPERATION_CODE")); + operation.setInitiatedBy(rs.getString("INITIATED_BY")); operation.setStatus(Operation.Status.valueOf(rs.getString("STATUS"))); operations.add(operation); } @@ -1345,7 +1349,7 @@ public class GenericOperationDAOImpl implements OperationDAO { public List getOperationsForDevice(int enrolmentId, PaginationRequest request) throws OperationManagementDAOException { Operation operation; - List operations = new ArrayList(); + List operations = new ArrayList<>(); String createdTo = null; String createdFrom = null; DateFormat simple = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); @@ -1363,74 +1367,75 @@ public class GenericOperationDAOImpl implements OperationDAO { Long updatedTo = request.getOperationLogFilters().getUpdatedDayTo(); List operationCode = request.getOperationLogFilters().getOperationCode(); List status = request.getOperationLogFilters().getStatus(); - String sql = "SELECT " + - "o.ID, " + - "TYPE, " + - "o.CREATED_TIMESTAMP, " + - "o.RECEIVED_TIMESTAMP, " + - "o.OPERATION_CODE, " + - "om.STATUS, " + - "om.ID AS OM_MAPPING_ID, " + - "om.UPDATED_TIMESTAMP " + - "FROM " + - "DM_OPERATION o " + - "INNER JOIN " + - "(SELECT dm.OPERATION_ID, " + - "dm.ID, " + - "dm.STATUS, " + - "dm.UPDATED_TIMESTAMP " + - "FROM " + - "DM_ENROLMENT_OP_MAPPING dm " + - "WHERE " + - "dm.ENROLMENT_ID = ?"; + StringBuilder sql = new StringBuilder("SELECT " + + "o.ID, " + + "TYPE, " + + "o.CREATED_TIMESTAMP, " + + "o.RECEIVED_TIMESTAMP, " + + "o.OPERATION_CODE, " + + "o.INITIATED_BY, " + + "om.STATUS, " + + "om.ID AS OM_MAPPING_ID, " + + "om.UPDATED_TIMESTAMP " + + "FROM " + + "DM_OPERATION o " + + "INNER JOIN " + + "(SELECT dm.OPERATION_ID, " + + "dm.ID, " + + "dm.STATUS, " + + "dm.UPDATED_TIMESTAMP " + + "FROM " + + "DM_ENROLMENT_OP_MAPPING dm " + + "WHERE " + + "dm.ENROLMENT_ID = ?"); if (updatedFrom != null && updatedFrom != 0 && updatedTo != null && updatedTo != 0) { - sql = sql + " AND dm.UPDATED_TIMESTAMP BETWEEN ? AND ?"; + sql.append(" AND dm.UPDATED_TIMESTAMP BETWEEN ? AND ?"); isUpdatedDayProvided = true; } - sql = sql + ") om ON o.ID = om.OPERATION_ID "; + sql.append(") om ON o.ID = om.OPERATION_ID "); if (createdFrom != null && !createdFrom.isEmpty() && createdTo != null && !createdTo.isEmpty()) { - sql = sql + " WHERE o.CREATED_TIMESTAMP BETWEEN ? AND ?"; + sql.append(" WHERE o.CREATED_TIMESTAMP BETWEEN ? AND ?"); isCreatedDayProvided = true; } if ((isCreatedDayProvided) && (status != null && !status.isEmpty())) { int size = status.size(); - sql = sql + " AND (om.STATUS = ? "; + sql.append(" AND (om.STATUS = ? "); for (int i = 0; i < size - 1; i++) { - sql = sql + " OR om.STATUS = ?"; + sql.append(" OR om.STATUS = ?"); } - sql = sql + ")"; + sql.append(")"); isStatusProvided = true; } else if ((!isCreatedDayProvided) && (status != null && !status.isEmpty())) { int size = status.size(); - sql = sql + " WHERE (om.STATUS = ? "; + sql.append(" WHERE (om.STATUS = ? "); for (int i = 0; i < size - 1; i++) { - sql = sql + " OR om.STATUS = ?"; + sql.append(" OR om.STATUS = ?"); } - sql = sql + ")"; + sql.append(")"); isStatusProvided = true; } if ((isCreatedDayProvided || isStatusProvided) && (operationCode != null && !operationCode.isEmpty())) { int size = operationCode.size(); - sql = sql + " AND (o.OPERATION_CODE = ? "; + sql.append(" AND (o.OPERATION_CODE = ? "); for (int i = 0; i < size - 1; i++) { - sql = sql + " OR o.OPERATION_CODE = ?"; + sql.append(" OR o.OPERATION_CODE = ?"); } - sql = sql + ")"; + sql.append(")"); isOperationCodeProvided = true; } else if ((!isCreatedDayProvided && !isStatusProvided) && (operationCode != null && !operationCode.isEmpty())) { int size = operationCode.size(); - sql = sql + " WHERE (o.OPERATION_CODE = ? "; + sql.append(" WHERE (o.OPERATION_CODE = ? "); for (int i = 0; i < size - 1; i++) { - sql = sql + " OR o.OPERATION_CODE = ?"; + sql.append(" OR o.OPERATION_CODE = ?"); } - sql = sql + ")"; + sql.append(")"); isOperationCodeProvided = true; } - sql = sql + " ORDER BY o.CREATED_TIMESTAMP DESC LIMIT ?,?"; + sql.append(" ORDER BY o.CREATED_TIMESTAMP DESC LIMIT ?,?"); try { Connection conn = OperationManagementDAOFactory.getConnection(); - try (PreparedStatement stmt = conn.prepareStatement(sql)) { + try (PreparedStatement stmt = conn.prepareStatement(sql.toString())) { int paramIndex = 1; stmt.setInt(paramIndex++, enrolmentId); if (isUpdatedDayProvided) { @@ -1442,15 +1447,13 @@ public class GenericOperationDAOImpl implements OperationDAO { stmt.setString(paramIndex++, createdTo); } if (isStatusProvided) { - int size = status.size(); - for (int i = 0; i < size; i++) { - stmt.setString(paramIndex++, status.get(i)); + for (String s : status) { + stmt.setString(paramIndex++, s); } } if (isOperationCodeProvided) { - int size = operationCode.size(); - for (int i = 0; i < size; i++) { - stmt.setString(paramIndex++, operationCode.get(i)); + for (String s : operationCode) { + stmt.setString(paramIndex++, s); } } stmt.setInt(paramIndex++, request.getStartIndex()); @@ -1468,6 +1471,7 @@ public class GenericOperationDAOImpl implements OperationDAO { new Timestamp((rs.getLong("UPDATED_TIMESTAMP") * 1000)).toString()); } operation.setCode(rs.getString("OPERATION_CODE")); + operation.setInitiatedBy(rs.getString("INITIATED_BY")); operation.setStatus(Operation.Status.valueOf(rs.getString("STATUS"))); OperationDAOUtil.setActivityId(operation, rs.getInt("ID")); operations.add(operation); @@ -1594,7 +1598,7 @@ public class GenericOperationDAOImpl implements OperationDAO { try { Connection connection = OperationManagementDAOFactory.getConnection(); stmt = connection.prepareStatement("SELECT o.ID, o.TYPE, o.CREATED_TIMESTAMP, o.RECEIVED_TIMESTAMP, " + - "o.OPERATION_CODE, om.ID AS OM_MAPPING_ID, om.UPDATED_TIMESTAMP FROM DM_OPERATION o " + + "o.OPERATION_CODE, o.INITIATED_BY, om.ID AS OM_MAPPING_ID, om.UPDATED_TIMESTAMP FROM DM_OPERATION o " + "INNER JOIN (SELECT * FROM DM_ENROLMENT_OP_MAPPING dm " + "WHERE dm.ENROLMENT_ID = ? AND dm.STATUS = ?) om ON o.ID = om.OPERATION_ID " + "ORDER BY om.UPDATED_TIMESTAMP ASC, om.ID ASC LIMIT 1"); @@ -1616,6 +1620,7 @@ public class GenericOperationDAOImpl implements OperationDAO { new Timestamp((rs.getLong("UPDATED_TIMESTAMP") * 1000)).toString()); } operation.setCode(rs.getString("OPERATION_CODE")); + operation.setInitiatedBy(rs.getString("INITIATED_BY")); operation.setStatus(Operation.Status.PENDING); OperationDAOUtil.setActivityId(operation, rs.getInt("ID")); } @@ -1636,10 +1641,10 @@ public class GenericOperationDAOImpl implements OperationDAO { List operations = new ArrayList<>(); try { Connection conn = OperationManagementDAOFactory.getConnection(); - String sql = "SELECT o.ID, TYPE, o.CREATED_TIMESTAMP, o.RECEIVED_TIMESTAMP, OPERATION_CODE, om.ID AS OM_MAPPING_ID, " + - "om.UPDATED_TIMESTAMP FROM (SELECT o.ID, TYPE, CREATED_TIMESTAMP, RECEIVED_TIMESTAMP, OPERATION_CODE " + - "FROM DM_OPERATION o WHERE o.TYPE = ?) o " + - "INNER JOIN (SELECT * FROM DM_ENROLMENT_OP_MAPPING dm " + + String sql = "SELECT o.ID, TYPE, o.CREATED_TIMESTAMP, o.RECEIVED_TIMESTAMP, OPERATION_CODE, o.INITIATED_BY," + + " om.ID AS OM_MAPPING_ID, om.UPDATED_TIMESTAMP FROM " + + "(SELECT o.ID, TYPE, CREATED_TIMESTAMP, RECEIVED_TIMESTAMP, OPERATION_CODE, INITIATED_BY " + + "FROM DM_OPERATION o WHERE o.TYPE = ?) o INNER JOIN (SELECT * FROM DM_ENROLMENT_OP_MAPPING dm " + "WHERE dm.ENROLMENT_ID = ? AND dm.STATUS = ?) om ON o.ID = om.OPERATION_ID ORDER BY o.CREATED_TIMESTAMP ASC"; stmt = conn.prepareStatement(sql); @@ -1660,6 +1665,7 @@ public class GenericOperationDAOImpl implements OperationDAO { new Timestamp((rs.getLong("UPDATED_TIMESTAMP") * 1000)).toString()); } operation.setCode(rs.getString("OPERATION_CODE")); + operation.setInitiatedBy(rs.getString("INITIATED_BY")); OperationDAOUtil.setActivityId(operation, rs.getInt("ID")); operations.add(operation); } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/Constants.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/Constants.java index 2be36d0acf..962082ce3a 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/Constants.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/Constants.java @@ -18,7 +18,14 @@ package org.wso2.carbon.device.mgt.core.report.mgt; +import org.wso2.carbon.utils.CarbonUtils; + +import java.io.File; + public class Constants { + public static final String DEFAULT_CONFIG_FILE_LOCATION = CarbonUtils.getCarbonConfigDirPath() + File.separator + + Constants.REPORT_MGT_CONFIG_XML_FILE; + public static final String REPORT_MGT_CONFIG_XML_FILE = "reporting-mgt.xml"; // device types public static final String ANDROID = "android"; public static final String IOS = "ios"; @@ -29,5 +36,14 @@ public class Constants { // OS version value generating properties public static final int NUM_OF_OS_VERSION_DIGITS= 5; public static final int NUM_OF_OS_VERSION_POSITIONS = 3; + public static final String BASIC_AUTH_HEADER_PREFIX = "Basic "; + public static final String HTTP_PROTOCOL = "http"; + public static final String HTTPS_PROTOCOL = "https"; + public static final String WSS_PROTOCOL = "wss"; + public static final String WS_PROTOCOL = "ws"; + public static final String SCHEME_SEPARATOR = "://"; + public static final String URI_SEPARATOR = "/"; + public static final String URI_QUERY_SEPARATOR = "?"; + public static final String COLON = ":"; } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/config/ReportMgtConfiguration.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/config/ReportMgtConfiguration.java new file mode 100644 index 0000000000..6c736e8679 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/config/ReportMgtConfiguration.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019, Entgra (pvt) Ltd. (http://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 org.wso2.carbon.device.mgt.core.report.mgt.config; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * Represents the Application Management Configuration. + */ +@XmlRootElement(name = "ReportManagementConfiguration") +public class ReportMgtConfiguration { + + private String datasourceName; + + @XmlElement(name = "DatasourceName", required = true) + public String getDatasourceName() { + return datasourceName; + } + + public void setDatasourceName(String datasourceName) { + this.datasourceName = datasourceName; + } + +} + diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/config/ReportMgtConfigurationManager.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/config/ReportMgtConfigurationManager.java new file mode 100644 index 0000000000..e47ba7cc84 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/config/ReportMgtConfigurationManager.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019, Entgra (pvt) Ltd. (http://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 org.wso2.carbon.device.mgt.core.report.mgt.config; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.exceptions.InvalidConfigurationException; +import org.wso2.carbon.device.mgt.common.exceptions.ReportManagementException; +import org.wso2.carbon.device.mgt.core.report.mgt.Constants; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Unmarshaller; +import java.io.File; + +/** + * ConfigurationManager is responsible for the managing Application Management related configurations. + */ +public class ReportMgtConfigurationManager { + + private static final Log log = LogFactory.getLog(ReportMgtConfigurationManager.class); + + private ReportMgtConfiguration configuration; + + private static String configPath; + + private static volatile ReportMgtConfigurationManager configurationManager; + + private ReportMgtConfigurationManager() { + + } + + public static ReportMgtConfigurationManager getInstance() { + if (configurationManager == null) { + synchronized (ReportMgtConfigurationManager.class) { + if (configurationManager == null) { + configurationManager = new ReportMgtConfigurationManager(); + try { + configurationManager.initConfig(); + } catch (ReportManagementException e) { + log.error(e); + } + } + } + } + return configurationManager; + } + + public static synchronized void setConfigLocation(String configPath) throws InvalidConfigurationException { + if (ReportMgtConfigurationManager.configPath == null) { + ReportMgtConfigurationManager.configPath = configPath; + } else { + throw new InvalidConfigurationException("Configuration path " + configPath + " is already defined"); + } + } + + private void initConfig() throws ReportManagementException { + try { + JAXBContext jaxbContext = JAXBContext.newInstance(ReportMgtConfiguration.class); + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + if (configPath == null) { + configPath = Constants.DEFAULT_CONFIG_FILE_LOCATION; + } + //TODO: Add validation for the configurations + this.configuration = (ReportMgtConfiguration) unmarshaller.unmarshal(new File(configPath)); + } catch (Exception e) { + log.error(e); + throw new InvalidConfigurationException("Error occurred while initializing application config: " + + configPath, e); + } + } + + public ReportMgtConfiguration getConfiguration() { + return configuration; + } + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/dao/common/ReportMgtConnectionManager.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/dao/common/ReportMgtConnectionManager.java new file mode 100644 index 0000000000..0d11461ef0 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/dao/common/ReportMgtConnectionManager.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019, Entgra (pvt) Ltd. (http://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 org.wso2.carbon.device.mgt.core.report.mgt.dao.common; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.exceptions.DBConnectionException; +import org.wso2.carbon.device.mgt.common.exceptions.IllegalTransactionStateException; +import org.wso2.carbon.device.mgt.core.report.mgt.config.ReportMgtConfigurationManager; + +import javax.naming.InitialContext; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * This class intends to act as the primary entity that hides all DAO instantiation related complexities and logic so + * that the business objection handling layer doesn't need to be aware of the same providing seamless plug-ability of + * different data sources, connection acquisition mechanisms as well as different forms of DAO implementations to the + * high-level implementations that require Application management related metadata persistence. + */ +public class ReportMgtConnectionManager { + + private static final Log log = LogFactory.getLog(ReportMgtConnectionManager.class); + + private static final ThreadLocal currentConnection = new ThreadLocal<>(); + private static DataSource dataSource; + + static { + String dataSourceName = ReportMgtConfigurationManager.getInstance().getConfiguration().getDatasourceName(); + init(dataSourceName); + } + + public static void init(String datasourceName) { + resolveDataSource(datasourceName); + } + + public static String getDatabaseType() { + try { + return dataSource.getConnection().getMetaData().getDatabaseProductName(); + } catch (SQLException e) { + log.error("Error occurred while retrieving config.datasource connection", e); + } + return null; + } + + /** + * Resolve the datasource from the datasource definition. + * + * @param dataSourceName Name of the datasource + * @return DataSource resolved by the datasource name + */ + public static DataSource resolveDataSource(String dataSourceName) { + try { + dataSource = InitialContext.doLookup(dataSourceName); + } catch (Exception e) { + throw new RuntimeException("Error in looking up data source: " + e.getMessage(), e); + } + return dataSource; + } + + public static void openDBConnection() throws DBConnectionException { + Connection conn = currentConnection.get(); + if (conn != null) { + throw new IllegalTransactionStateException("Database connection has already been obtained."); + } + try { + conn = dataSource.getConnection(); + } catch (SQLException e) { + throw new DBConnectionException("Failed to get a database connection.", e); + } + currentConnection.set(conn); + } + + public static Connection getDBConnection() throws DBConnectionException { + if (dataSource == null) { + String dataSourceName = ReportMgtConfigurationManager.getInstance().getConfiguration().getDatasourceName(); + init(dataSourceName); + } + Connection conn = currentConnection.get(); + if (conn == null) { + try { + conn = dataSource.getConnection(); + currentConnection.set(conn); + } catch (SQLException e) { + throw new DBConnectionException("Failed to get database connection.", e); + } + } + return conn; + } + + + public static void closeDBConnection() { + Connection conn = currentConnection.get(); + if (conn == null) { + throw new IllegalTransactionStateException("Database connection is not active."); + } + try { + conn.close(); + } catch (SQLException e) { + log.error("Error occurred while closing the connection", e); + } + currentConnection.remove(); + } + + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/util/HttpReportingUtil.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/util/HttpReportingUtil.java index 920b4e4972..5080ab2a24 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/util/HttpReportingUtil.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/util/HttpReportingUtil.java @@ -17,6 +17,10 @@ package org.wso2.carbon.device.mgt.core.util; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; @@ -26,11 +30,21 @@ import org.apache.http.impl.client.HttpClients; import org.apache.http.protocol.HTTP; import org.wso2.carbon.device.mgt.common.exceptions.EventPublishingException; import org.wso2.carbon.device.mgt.core.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.core.report.mgt.Constants; +import javax.ws.rs.core.PathSegment; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class HttpReportingUtil { + private static final Log log = LogFactory.getLog(HttpReportingUtil.class); private static final String IS_EVENT_PUBLISHING_ENABLED = "isEventPublishingEnabled"; public static String getReportingHost() { @@ -51,6 +65,115 @@ public class HttpReportingUtil { "invoking API. API endpoint: " + endpoint, e); } } + public static String getRequestSubPathFromEnd(URI requestUri, int position) { + String[] pathList = requestUri.getPath().split("/"); + if (pathList.length - 1 >= position) { + return pathList[pathList.length - 1 - position]; + } + return null; + } + + public static String getRequestSubPath(String fullPath, int position) { + String[] pathList = fullPath.split("/"); + if (pathList.length - 1 > position) { + String path = pathList[position + 1]; + if(path.contains(Constants.URI_QUERY_SEPARATOR)) { + path = path.substring(0, path.indexOf(Constants.URI_QUERY_SEPARATOR)); + } + return path; + } + return null; + } + + public static String getResponseString(HttpResponse response) throws IOException { + try (BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))) { + StringBuilder result = new StringBuilder(); + String line; + while ((line = rd.readLine()) != null) { + result.append(line); + } + return result.toString(); + } + } + public static boolean isQueryParamExist(String param, URI request) { + Map> queryMap = getQueryMap(request); + return queryMap.containsKey(param); + } + public static String getFirstQueryValue(String param, Map> queryMap) { + List valueList = queryMap.get(param); + String firstValue = null; + if(valueList != null) { + firstValue = valueList.get(0); + } + return firstValue; + } + public static Map> getQueryMap(String uri) { + String query = getQueryFromURIPath(uri); + Map> map = new HashMap<>(); + if (query != null) { + String[] params = query.split("&"); + for (String param : params) { + String[] paramArr = param.split("="); + if (paramArr.length == 2) { + String name = paramArr[0]; + String value = paramArr[1]; + if (!map.containsKey(name)) { + List valueList = new ArrayList<>(); + map.put(name, valueList); + } + map.get(name).add(value); + } + } + } + return map; + } + public static Map> getQueryMap(URI request) { + String query = request.getQuery(); + Map> map = new HashMap<>(); + if (query != null) { + String[] params = query.split("&"); + for (String param : params) { + String[] paramArr = param.split("="); + if (paramArr.length == 2) { + String name = paramArr[0]; + String value = paramArr[1]; + if (!map.containsKey(name)) { + List valueList = new ArrayList<>(); + map.put(name, valueList); + } + map.get(name).add(value); + } + } + } + return map; + } + public static String getQueryFromURIPath(String uri) { + String query = null; + if (uri.length() > "?".length() && uri.contains("?")) { + query = uri.substring(uri.lastIndexOf("?") + "?".length()); + } + if (StringUtils.isEmpty(query)) { + query = null; + } + return query; + } + + public static String getMemeType(HttpResponse response) { + String memeType = ""; + Header contentType = response.getEntity().getContentType(); + if (contentType != null) { + memeType = contentType.getValue().split(";")[0].trim(); + } + return memeType; + } + public static String getGrafanaBaseUrl(String scheme) { + String host = "localhost"; + String port = "3000"; + if (scheme.equals(Constants.HTTP_PROTOCOL)) { + port = "3000"; + } + return host + Constants.COLON + port; + } public static boolean isPublishingEnabledForTenant() { Object configuration = DeviceManagerUtil.getConfiguration(IS_EVENT_PUBLISHING_ENABLED); diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/DefaultTokenHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/DefaultTokenHandler.java index 9667c25000..6c182b9e1d 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/DefaultTokenHandler.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/DefaultTokenHandler.java @@ -140,6 +140,7 @@ public class DefaultTokenHandler extends HttpServlet { ProxyResponse proxyResponse = new ProxyResponse(); proxyResponse.setCode(HttpStatus.SC_OK); + proxyResponse.setStatus(ProxyResponse.Status.SUCCESS); proxyResponse.setData(payload); return proxyResponse; } diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/GrafanaHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/GrafanaHandler.java new file mode 100644 index 0000000000..4b664e0fef --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/GrafanaHandler.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2019, 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.beans.AuthData; +import io.entgra.ui.request.interceptor.beans.ProxyResponse; +import io.entgra.ui.request.interceptor.util.GrafanaHandlerUtil; +import io.entgra.ui.request.interceptor.util.HandlerConstants; +import io.entgra.ui.request.interceptor.util.HandlerUtil; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.*; +import org.apache.http.impl.client.CloseableHttpClient; +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; +import org.wso2.carbon.device.mgt.core.common.util.HttpUtil; +import org.wso2.carbon.device.mgt.core.grafana.mgt.exception.GrafanaEnvVariablesNotDefined; +import org.wso2.carbon.device.mgt.core.grafana.mgt.util.GrafanaUtil; +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.ws.rs.ProcessingException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; + +@MultipartConfig +@WebServlet( + name = "GrafanaRequestHandler", + description = "This servlet intercepts the iframe requests initiated from the user interface and validate before" + + " forwarding to the backend", + urlPatterns = { + "/grafana/*" + } +) +public class GrafanaHandler extends HttpServlet { + private static final Log log = LogFactory.getLog(GrafanaHandler.class); + private static final long serialVersionUID = -6508020875358160165L; + private static AuthData authData; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { + try { + if (validateRequest(req, resp)) { + HttpGet grafanaRequest = new HttpGet(); + HandlerUtil.copyRequestHeaders(req, grafanaRequest, true); + if (!GrafanaUtil.isGrafanaAPI(req.getRequestURI())) { + proxyPassGrafanaRequest(grafanaRequest, resp, req); + return; + } + grafanaRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); + ProxyResponse grafanaAPIResponse = executeGrafanaAPIRequest(grafanaRequest, req); + String keyManagerUrl = HandlerUtil.getKeyManagerUrl(req.getScheme()); + if (HandlerConstants.TOKEN_IS_EXPIRED.equals(grafanaAPIResponse.getExecutorResponse())) { + grafanaAPIResponse = HandlerUtil.retryRequestWithRefreshedToken(req, grafanaRequest, keyManagerUrl); + if (!HandlerUtil.isResponseSuccessful(grafanaAPIResponse)) { + HandlerUtil.handleError(resp, grafanaAPIResponse); + return; + } + } + if (grafanaAPIResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { + if (grafanaAPIResponse.getCode() == HttpStatus.SC_UNAUTHORIZED) { + grafanaAPIResponse = HandlerUtil.retryRequestWithRefreshedToken(req, grafanaRequest, keyManagerUrl); + if (!HandlerUtil.isResponseSuccessful(grafanaAPIResponse)) { + HandlerUtil.handleError(resp, grafanaAPIResponse); + return; + } + } else { + log.error("Error occurred while invoking the GET API endpoint."); + HandlerUtil.handleError(resp, grafanaAPIResponse); + return; + } + } + handleSuccess(resp, grafanaAPIResponse); + } + } catch (ProcessingException e) { + String msg = "Grafana server is down or invalid grafana dashboard url provided"; + log.error(msg, e); + } + catch (IOException e) { + log.error("Error occurred when processing Iframe request.", e); + } + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) { + try { + if (validateRequest(req, resp)) { + HttpPost grafanaRequest = new HttpPost(); + HandlerUtil.generateRequestEntity(req, grafanaRequest); + HandlerUtil.copyRequestHeaders(req, grafanaRequest, true); + if (!GrafanaUtil.isGrafanaAPI(req.getRequestURI())) { + proxyPassGrafanaRequest(grafanaRequest, resp, req); + return; + } + grafanaRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); + ProxyResponse grafanaAPIResponse = executeGrafanaAPIRequest(grafanaRequest, req); + if (HandlerConstants.TOKEN_IS_EXPIRED.equals(grafanaAPIResponse.getExecutorResponse())) { + String keyManagerUrl = HandlerUtil.getKeyManagerUrl(req.getScheme()); + grafanaAPIResponse = HandlerUtil.retryRequestWithRefreshedToken(req, grafanaRequest, keyManagerUrl); + if (!HandlerUtil.isResponseSuccessful(grafanaAPIResponse)) { + handleError(resp, grafanaAPIResponse); + return; + } + } + if (grafanaAPIResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { + log.error("Error occurred while invoking the POST API endpoint."); + handleError(resp, grafanaAPIResponse); + return; + } + handleSuccess(resp, grafanaAPIResponse); + } + } catch (FileUploadException e) { + log.error("Error occurred when processing Multipart POST request.", e); + } catch (ProcessingException e) { + String msg = "Grafana server is down or invalid grafana dashboard url provided"; + log.error(msg, e); + } catch (IOException e) { + log.error("Error occurred when processing Iframe request.", e); + } + } + + private void handleSuccess(HttpServletResponse resp, ProxyResponse grafanaAPIResponse) throws IOException{ + String contentType = HandlerUtil.getHeaderValue(HttpHeaders.CONTENT_TYPE, grafanaAPIResponse.getHeaders()); + resp.setContentType(contentType); + resp.setStatus(grafanaAPIResponse.getCode()); + addXFrameOptionsHeaders(resp); + resp.getWriter().print(grafanaAPIResponse.getData()); + } + + private void handleError(HttpServletResponse resp, int errCode, String errMsg) throws IOException { + resp.sendError(errCode, errMsg); + } + + private void handleError(HttpServletResponse resp, ProxyResponse proxyResponse) throws IOException { + resp.sendError(proxyResponse.getCode()); + } + + /*** + * Validates the incoming request. + * + * @param req {@link HttpServletRequest} + * @param resp {@link HttpServletResponse} + * @return If request is a valid one, returns TRUE, otherwise return FALSE + * @throws IOException If and error occurs while witting error response to client side + */ + private boolean validateRequest(HttpServletRequest req, HttpServletResponse resp) + throws IOException { + + if (req.getMethod() == null) { + String errMsg = "Bad Request, Request method is empty"; + log.error(errMsg); + handleError(resp, HttpStatus.SC_BAD_REQUEST, errMsg); + return false; + } + if (HandlerUtil.isPropertyDefined(HandlerConstants.IOT_REPORTING_WEBAPP_HOST_ENV_VAR)) { + String errMsg = "Reporting Endpoint is not defined in the iot-server.sh properly."; + log.error(errMsg); + resp.sendError(500, errMsg); + return false; + } + + HttpSession session = req.getSession(false); + if (session == null) { + String errMsg = "Unauthorized, You are not logged in. Please log in to the portal"; + log.error(errMsg); + handleError(resp, HttpStatus.SC_UNAUTHORIZED, errMsg); + return false; + } + + authData = (AuthData) session.getAttribute(HandlerConstants.SESSION_AUTH_DATA_KEY); + if (authData == null) { + String errMsg = "Unauthorized, Access token not found in the current session"; + log.error(errMsg); + handleError(resp, HttpStatus.SC_UNAUTHORIZED, errMsg); + return false; + } + + return true; + } + + private ProxyResponse executeGrafanaAPIRequest(HttpRequestBase requestBase, HttpServletRequest request) + throws IOException { + URI grafanaUri = HttpUtil.createURI(generateGrafanaAPIUrl(request)); + requestBase.setURI(grafanaUri); + return HandlerUtil.execute(requestBase); + } + + private String generateGrafanaAPIUrl(HttpServletRequest request) { + String apiBase = generateGrafanaAPIBase(request); + String grafanaUri = getURIWithQuery(request); + return apiBase + grafanaUri; + } + + private String generateGrafanaAPIBase(HttpServletRequest request) { + return HandlerUtil.getIOTGatewayBase(request) + HandlerConstants.GRAFANA_API; + } + + private String getURIWithQuery(HttpServletRequest request) { + String uri = request.getPathInfo(); + if (request.getQueryString() != null) { + uri += HandlerConstants.URI_QUERY_SEPARATOR + request.getQueryString(); + } + return uri; + } + private void proxyPassGrafanaRequest(HttpRequestBase requestBase, HttpServletResponse response, + HttpServletRequest request) throws IOException { + try (CloseableHttpClient client = HandlerUtil.getHttpClient()) { + String grafanaUriStr = GrafanaHandlerUtil.generateGrafanaUrl(HttpUtil.createURI(getURIWithQuery(request)), + GrafanaUtil.getGrafanaHTTPBase(request.getScheme())); + URI grafanaURI = HttpUtil.createURI(grafanaUriStr); + requestBase.setURI(grafanaURI); + HttpResponse grafanaResponse = invokeGrafanaAPI(client, requestBase); + forwardGrafanaResponse(grafanaResponse, response); + } catch (GrafanaEnvVariablesNotDefined e) { + handleError(response, HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (GrafanaManagementException e) { + String errMsg = "Error occurred while retrieving grafana user credentials"; + log.error(errMsg, e); + handleError(response, HttpStatus.SC_INTERNAL_SERVER_ERROR, errMsg); + } + } + + private HttpResponse invokeGrafanaAPI(HttpClient client, HttpRequestBase request) throws IOException, GrafanaManagementException { + setBasicAuthHeader(request); + return client.execute(request); + } + + private void setBasicAuthHeader(HttpRequestBase request) throws GrafanaManagementException { + String basicAuth = GrafanaUtil.getBasicAuthBase64Header(); + request.setHeader(HttpHeaders.AUTHORIZATION, basicAuth); + } + + private void forwardGrafanaResponse(HttpResponse grafanaResponse, HttpServletResponse response) throws IOException { + InputStream responseContent = grafanaResponse.getEntity().getContent(); + String grafanaContentType = HandlerUtil.getMemeType(grafanaResponse); + response.setHeader(HttpHeaders.CONTENT_TYPE, grafanaContentType); + addXFrameOptionsHeaders(response); + byte[] buffer = new byte[10240]; + try (InputStream input = responseContent; OutputStream output = response.getOutputStream()) { + for (int length = 0; (length = input.read(buffer)) > 0;) { + output.write(buffer, 0, length); + } + } + + } + + private void addXFrameOptionsHeaders(HttpServletResponse response) { + response.setHeader(HandlerConstants.X_FRAME_OPTIONS, HandlerConstants.X_FRAME_OPTIONS_SAMEORIGIN); + } + +} diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/InvokerHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/InvokerHandler.java index 7cfe352548..045aac0b5a 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/InvokerHandler.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/InvokerHandler.java @@ -18,37 +18,21 @@ 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.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; -import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.fileupload.servlet.ServletFileUpload; 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.HttpDelete; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.cookie.SM; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.InputStreamEntity; -import org.apache.http.entity.StringEntity; -import org.apache.http.entity.mime.HttpMultipartMode; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.entity.mime.content.InputStreamBody; import io.entgra.ui.request.interceptor.beans.ProxyResponse; - import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; @@ -56,8 +40,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; -import java.util.Enumeration; -import java.util.List; @MultipartConfig @WebServlet( @@ -79,14 +61,15 @@ public class InvokerHandler extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) { try { if (validateRequest(req, resp)) { - HttpPost postRequest = new HttpPost(generateBackendRequestURL(req)); - generateRequestEntity(req, postRequest); + HttpPost postRequest = new HttpPost(HandlerUtil.generateBackendRequestURL(req, apiEndpoint)); + HandlerUtil.generateRequestEntity(req, postRequest); postRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); ProxyResponse proxyResponse = HandlerUtil.execute(postRequest); if (HandlerConstants.TOKEN_IS_EXPIRED.equals(proxyResponse.getExecutorResponse())) { - proxyResponse = HandlerUtil.retryRequestWithRefreshedToken(req, resp, postRequest, kmManagerUrl); - if (proxyResponse == null) { + proxyResponse = HandlerUtil.retryRequestWithRefreshedToken(req, postRequest, kmManagerUrl); + if (!HandlerUtil.isResponseSuccessful(proxyResponse)) { + HandlerUtil.handleError(resp, proxyResponse); return; } } @@ -108,19 +91,24 @@ public class InvokerHandler extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) { try { if (validateRequest(req, resp)) { - HttpGet getRequest = new HttpGet(generateBackendRequestURL(req)); - copyRequestHeaders(req, getRequest, false); + HttpGet getRequest = new HttpGet(HandlerUtil.generateBackendRequestURL(req, apiEndpoint)); + HandlerUtil.copyRequestHeaders(req, getRequest, false); getRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); ProxyResponse proxyResponse = HandlerUtil.execute(getRequest); if (HandlerConstants.TOKEN_IS_EXPIRED.equals(proxyResponse.getExecutorResponse())) { - proxyResponse = HandlerUtil.retryRequestWithRefreshedToken(req, resp, getRequest, kmManagerUrl); - if (proxyResponse == null) { + proxyResponse = HandlerUtil.retryRequestWithRefreshedToken(req, getRequest, kmManagerUrl); + if (!HandlerUtil.isResponseSuccessful(proxyResponse)) { + HandlerUtil.handleError(resp, proxyResponse); return; } } if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { if (proxyResponse.getCode() == HttpStatus.SC_UNAUTHORIZED) { - proxyResponse = HandlerUtil.retryRequestWithRefreshedToken(req, resp, getRequest, kmManagerUrl); + proxyResponse = HandlerUtil.retryRequestWithRefreshedToken(req, getRequest, kmManagerUrl); + if (!HandlerUtil.isResponseSuccessful(proxyResponse)) { + HandlerUtil.handleError(resp, proxyResponse); + return; + } } else { log.error("Error occurred while invoking the GET API endpoint."); HandlerUtil.handleError(resp, proxyResponse); @@ -138,13 +126,14 @@ public class InvokerHandler extends HttpServlet { protected void doHead(HttpServletRequest req, HttpServletResponse resp) { try { if (validateRequest(req, resp)) { - HttpHead headRequest = new HttpHead(generateBackendRequestURL(req)); - copyRequestHeaders(req, headRequest, false); + HttpHead headRequest = new HttpHead(HandlerUtil.generateBackendRequestURL(req, apiEndpoint)); + HandlerUtil.copyRequestHeaders(req, headRequest, false); headRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); ProxyResponse proxyResponse = HandlerUtil.execute(headRequest); if (HandlerConstants.TOKEN_IS_EXPIRED.equals(proxyResponse.getExecutorResponse())) { - proxyResponse = HandlerUtil.retryRequestWithRefreshedToken(req, resp, headRequest, kmManagerUrl); - if (proxyResponse == null) { + proxyResponse = HandlerUtil.retryRequestWithRefreshedToken(req, headRequest, kmManagerUrl); + if (!HandlerUtil.isResponseSuccessful(proxyResponse)) { + HandlerUtil.handleError(resp, proxyResponse); return; } } @@ -164,14 +153,15 @@ public class InvokerHandler extends HttpServlet { protected void doPut(HttpServletRequest req, HttpServletResponse resp) { try { if (validateRequest(req, resp)) { - HttpPut putRequest = new HttpPut(generateBackendRequestURL(req)); - generateRequestEntity(req, putRequest); + HttpPut putRequest = new HttpPut(HandlerUtil.generateBackendRequestURL(req, apiEndpoint)); + HandlerUtil.generateRequestEntity(req, putRequest); putRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); ProxyResponse proxyResponse = HandlerUtil.execute(putRequest); if (HandlerConstants.TOKEN_IS_EXPIRED.equals(proxyResponse.getExecutorResponse())) { - proxyResponse = HandlerUtil.retryRequestWithRefreshedToken(req, resp, putRequest, kmManagerUrl); - if (proxyResponse == null) { + proxyResponse = HandlerUtil.retryRequestWithRefreshedToken(req, putRequest, kmManagerUrl); + if (!HandlerUtil.isResponseSuccessful(proxyResponse)) { + HandlerUtil.handleError(resp, proxyResponse); return; } } @@ -193,13 +183,14 @@ public class InvokerHandler extends HttpServlet { protected void doDelete(HttpServletRequest req, HttpServletResponse resp) { try { if (validateRequest(req, resp)) { - HttpDelete deleteRequest = new HttpDelete(generateBackendRequestURL(req)); - copyRequestHeaders(req, deleteRequest, false); + HttpDelete deleteRequest = new HttpDelete(HandlerUtil.generateBackendRequestURL(req, apiEndpoint)); + HandlerUtil.copyRequestHeaders(req, deleteRequest, false); deleteRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); ProxyResponse proxyResponse = HandlerUtil.execute(deleteRequest); if (HandlerConstants.TOKEN_IS_EXPIRED.equals(proxyResponse.getExecutorResponse())) { - proxyResponse = HandlerUtil.retryRequestWithRefreshedToken(req, resp, deleteRequest, kmManagerUrl); - if (proxyResponse == null) { + proxyResponse = HandlerUtil.retryRequestWithRefreshedToken(req, deleteRequest, kmManagerUrl); + if (!HandlerUtil.isResponseSuccessful(proxyResponse)) { + HandlerUtil.handleError(resp, proxyResponse); return; } } @@ -215,83 +206,6 @@ public class InvokerHandler extends HttpServlet { } } - /** - * Generate te request entity for POST and PUT requests from the incoming request. - * - * @param req incoming {@link HttpServletRequest}. - * @param proxyRequest proxy request instance. - * @throws FileUploadException If unable to parse the incoming request for multipart content extraction. - * @throws IOException If error occurred while generating the request body. - */ - private void generateRequestEntity(HttpServletRequest req, HttpEntityEnclosingRequestBase proxyRequest) - throws FileUploadException, IOException { - if (ServletFileUpload.isMultipartContent(req)) { - ServletFileUpload servletFileUpload = new ServletFileUpload(new DiskFileItemFactory()); - List fileItemList = servletFileUpload.parseRequest(req); - MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create(); - entityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); - for (FileItem item: fileItemList) { - if (!item.isFormField()) { - entityBuilder.addPart(item.getFieldName(), new InputStreamBody(item.getInputStream(), - ContentType.create(item.getContentType()), item.getName())); - } else { - entityBuilder.addTextBody(item.getFieldName(), item.getString()); - } - } - proxyRequest.setEntity(entityBuilder.build()); - copyRequestHeaders(req, proxyRequest, false); - } else { - if (StringUtils.isNotEmpty(req.getHeader(HttpHeaders.CONTENT_LENGTH)) || - StringUtils.isNotEmpty(req.getHeader(HttpHeaders.TRANSFER_ENCODING))) { - InputStreamEntity entity = new InputStreamEntity(req.getInputStream(), - Long.parseLong(req.getHeader(HttpHeaders.CONTENT_LENGTH))); - proxyRequest.setEntity(entity); - } - copyRequestHeaders(req, proxyRequest, true); - } - } - - /** - * Generates the target URL for the proxy request. - * - * @param req incoming {@link HttpServletRequest} - * @return Target URL - */ - private String generateBackendRequestURL(HttpServletRequest req) { - StringBuilder urlBuilder = new StringBuilder(); - urlBuilder.append(apiEndpoint).append(HandlerConstants.API_COMMON_CONTEXT) - .append(req.getPathInfo().replace(" ", "%20")); - if (StringUtils.isNotEmpty(req.getQueryString())) { - urlBuilder.append("?").append(req.getQueryString()); - } - return urlBuilder.toString(); - } - - /** - * Copy incoming request headers to the proxy request. - * - * @param req incoming {@link HttpServletRequest} - * @param httpRequest proxy request instance. - * @param preserveContentType true if content type header needs to be preserved. - * This should be set to false when handling multipart requests as Http - * client will generate the Content-Type header automatically. - */ - private void copyRequestHeaders(HttpServletRequest req, HttpRequestBase httpRequest, boolean preserveContentType) { - Enumeration headerNames = req.getHeaderNames(); - while (headerNames.hasMoreElements()) { - String headerName = headerNames.nextElement(); - if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH) || - headerName.equalsIgnoreCase(SM.COOKIE) || - (!preserveContentType && headerName.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE))) { - continue; - } - Enumeration headerValues = req.getHeaders(headerName); - while (headerValues.hasMoreElements()) { - httpRequest.setHeader(headerName, headerValues.nextElement()); - } - } - } - /*** * Validates the incoming request. * @@ -305,8 +219,7 @@ public class InvokerHandler extends HttpServlet { apiEndpoint = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_GW_HOST_ENV_VAR) + HandlerConstants.COLON + HandlerUtil.getGatewayPort(req.getScheme()); - kmManagerUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_KM_HOST_ENV_VAR) - + HandlerConstants.COLON + HandlerUtil.getKeymanagerPort(req.getScheme()); + kmManagerUrl = HandlerUtil.getKeyManagerUrl(req.getScheme()); if (HandlerConstants.REPORTS.equalsIgnoreCase(req.getHeader(HandlerConstants.APP_NAME))){ apiEndpoint = System.getProperty("iot.reporting.webapp.host"); @@ -339,107 +252,4 @@ public class InvokerHandler extends HttpServlet { return true; } - /** - * Retry request again after refreshing the access token - * - * @param req incoming {@link HttpServletRequest} - * @param resp resp {@link HttpServletResponse} - * @param httpRequest subclass of {@link HttpRequestBase} related to the current request. - * @return {@link ProxyResponse} if successful and null if failed. - * @throws IOException If an error occurs when try to retry the request. - */ - private ProxyResponse retryRequestWithRefreshedToken(HttpServletRequest req, HttpServletResponse resp, - HttpRequestBase httpRequest) throws IOException { - if (refreshToken(req, resp)) { - httpRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); - ProxyResponse proxyResponse = HandlerUtil.execute(httpRequest); - if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { - log.error("Error occurred while invoking the API after refreshing the token."); - HandlerUtil.handleError(resp, proxyResponse); - return null; - } - return proxyResponse; - } - return null; - } - - /*** - * This method is responsible to get the refresh token - * - * @param req {@link HttpServletRequest} - * @param resp {@link HttpServletResponse} - * @return If successfully renew tokens, returns TRUE otherwise return FALSE - * @throws IOException If an error occurs while witting error response to client side or invoke token renewal API - */ - private static boolean refreshToken(HttpServletRequest req, HttpServletResponse resp) - throws IOException { - if (log.isDebugEnabled()) { - log.debug("refreshing the token"); - } - - HttpPost tokenEndpoint = new HttpPost( - kmManagerUrl + HandlerConstants.TOKEN_ENDPOINT); - HttpSession session = req.getSession(false); - if (session == null) { - log.error("Couldn't find a session, hence it is required to login and proceed."); - handleError(resp, HttpStatus.SC_UNAUTHORIZED); - return false; - } - - StringEntity tokenEndpointPayload = new StringEntity( - "grant_type=refresh_token&refresh_token=" + authData.getRefreshToken() + "&scope=PRODUCTION", - ContentType.APPLICATION_FORM_URLENCODED); - - tokenEndpoint.setEntity(tokenEndpointPayload); - String encodedClientApp = authData.getEncodedClientApp(); - tokenEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + - encodedClientApp); - tokenEndpoint.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString()); - - ProxyResponse tokenResultResponse = HandlerUtil.execute(tokenEndpoint); - if (tokenResultResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { - log.error("Error occurred while refreshing access token."); - HandlerUtil.handleError(resp, tokenResultResponse); - return false; - } - - JsonParser jsonParser = new JsonParser(); - JsonElement jTokenResult = jsonParser.parse(tokenResultResponse.getData()); - - if (jTokenResult.isJsonObject()) { - JsonObject jTokenResultAsJsonObject = jTokenResult.getAsJsonObject(); - AuthData newAuthData = new AuthData(); - - newAuthData.setAccessToken(jTokenResultAsJsonObject.get("access_token").getAsString()); - newAuthData.setRefreshToken(jTokenResultAsJsonObject.get("refresh_token").getAsString()); - newAuthData.setScope(jTokenResultAsJsonObject.get("scope").getAsString()); - newAuthData.setClientId(authData.getClientId()); - newAuthData.setClientSecret(authData.getClientSecret()); - newAuthData.setEncodedClientApp(authData.getEncodedClientApp()); - newAuthData.setUsername(authData.getUsername()); - authData = newAuthData; - session.setAttribute(HandlerConstants.SESSION_AUTH_DATA_KEY, newAuthData); - return true; - } - - log.error("Error Occurred in token renewal process."); - handleError(resp, HttpStatus.SC_INTERNAL_SERVER_ERROR); - return false; - } - - /** - * Handle error requests - * - * @param resp {@link HttpServletResponse} - * @param errorCode HTTP error status code - * @throws IOException If error occurred when trying to send the error response. - */ - private static void handleError(HttpServletResponse resp, int errorCode) - throws IOException { - ProxyResponse proxyResponse = new ProxyResponse(); - proxyResponse.setCode(errorCode); - proxyResponse.setExecutorResponse( - HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil.getStatusKey(errorCode)); - HandlerUtil.handleError(resp, proxyResponse); - } } 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 953fbf5a5c..f41f94a764 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 @@ -123,6 +123,7 @@ public class LoginHandler extends HttpServlet { if (getTokenAndPersistInSession(req, resp, clientId, clientSecret, encodedClientApp, scopes)) { ProxyResponse proxyResponse = new ProxyResponse(); + proxyResponse.setStatus(ProxyResponse.Status.SUCCESS); proxyResponse.setCode(HttpStatus.SC_OK); HandlerUtil.handleSuccess(resp, proxyResponse); return; @@ -210,7 +211,7 @@ public class LoginHandler extends HttpServlet { + HandlerConstants.COLON + HandlerUtil.getCorePort(req.getScheme()); uiConfigUrl = iotCoreUrl + HandlerConstants.UI_CONFIG_ENDPOINT; kmManagerUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_KM_HOST_ENV_VAR) - + HandlerConstants.COLON + HandlerUtil.getKeymanagerPort(req.getScheme()); + + HandlerConstants.COLON + HandlerUtil.getKeyManagerPort(req.getScheme()); if (username == null || password == null) { String msg = "Invalid login request. Username or Password is not received for login request."; diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/LogoutHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/LogoutHandler.java index 6cb7aed757..bbf82164a2 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/LogoutHandler.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/LogoutHandler.java @@ -45,6 +45,7 @@ public class LogoutHandler extends HttpServlet { } ProxyResponse proxyResponse = new ProxyResponse(); + proxyResponse.setStatus(ProxyResponse.Status.SUCCESS); proxyResponse.setCode(HttpStatus.SC_OK); try { HandlerUtil.handleSuccess(resp, proxyResponse); diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/OTPInvokerHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/OTPInvokerHandler.java index c852cce259..44dbe7882e 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/OTPInvokerHandler.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/OTPInvokerHandler.java @@ -20,37 +20,22 @@ 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.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; -import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.fileupload.servlet.ServletFileUpload; 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.HttpDelete; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.cookie.SM; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.InputStreamEntity; -import org.apache.http.entity.mime.HttpMultipartMode; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.entity.mime.content.InputStreamBody; import io.entgra.ui.request.interceptor.beans.ProxyResponse; - import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.Enumeration; -import java.util.List; @WebServlet( name = "OTPRequestHandlerServlet", @@ -70,7 +55,7 @@ public class OTPInvokerHandler extends HttpServlet { try { if (validateRequest(req, resp)) { HttpPost postRequest = new HttpPost(HandlerUtil.generateBackendRequestURL(req, apiEndpoint)); - generateRequestEntity(req, postRequest); + HandlerUtil.generateRequestEntity(req, postRequest); ProxyResponse proxyResponse = HandlerUtil.execute(postRequest); if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { @@ -92,7 +77,7 @@ public class OTPInvokerHandler extends HttpServlet { try { if (validateRequest(req, resp)) { HttpGet getRequest = new HttpGet(HandlerUtil.generateBackendRequestURL(req, apiEndpoint)); - copyRequestHeaders(req, getRequest, false); + HandlerUtil.copyRequestHeaders(req, getRequest, false); ProxyResponse proxyResponse = HandlerUtil.execute(getRequest); if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { @@ -112,7 +97,7 @@ public class OTPInvokerHandler extends HttpServlet { try { if (validateRequest(req, resp)) { HttpHead headRequest = new HttpHead(HandlerUtil.generateBackendRequestURL(req, apiEndpoint)); - copyRequestHeaders(req, headRequest, false); + HandlerUtil.copyRequestHeaders(req, headRequest, false); ProxyResponse proxyResponse = HandlerUtil.execute(headRequest); if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { @@ -132,7 +117,7 @@ public class OTPInvokerHandler extends HttpServlet { try { if (validateRequest(req, resp)) { HttpPut putRequest = new HttpPut(HandlerUtil.generateBackendRequestURL(req, apiEndpoint)); - generateRequestEntity(req, putRequest); + HandlerUtil.generateRequestEntity(req, putRequest); ProxyResponse proxyResponse = HandlerUtil.execute(putRequest); if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { @@ -154,7 +139,7 @@ public class OTPInvokerHandler extends HttpServlet { try { if (validateRequest(req, resp)) { HttpDelete deleteRequest = new HttpDelete(HandlerUtil.generateBackendRequestURL(req, apiEndpoint)); - copyRequestHeaders(req, deleteRequest, false); + HandlerUtil.copyRequestHeaders(req, deleteRequest, false); ProxyResponse proxyResponse = HandlerUtil.execute(deleteRequest); if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { @@ -169,67 +154,6 @@ public class OTPInvokerHandler extends HttpServlet { } } - /** - * Generate te request entity for POST and PUT requests from the incoming request. - * - * @param req incoming {@link HttpServletRequest}. - * @param proxyRequest proxy request instance. - * @throws FileUploadException If unable to parse the incoming request for multipart content extraction. - * @throws IOException If error occurred while generating the request body. - */ - private void generateRequestEntity(HttpServletRequest req, HttpEntityEnclosingRequestBase proxyRequest) - throws FileUploadException, IOException { - if (ServletFileUpload.isMultipartContent(req)) { - ServletFileUpload servletFileUpload = new ServletFileUpload(new DiskFileItemFactory()); - List fileItemList = servletFileUpload.parseRequest(req); - MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create(); - entityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); - for (FileItem item: fileItemList) { - if (!item.isFormField()) { - entityBuilder.addPart(item.getFieldName(), new InputStreamBody(item.getInputStream(), - ContentType.create(item.getContentType()), item.getName())); - } else { - entityBuilder.addTextBody(item.getFieldName(), item.getString()); - } - } - proxyRequest.setEntity(entityBuilder.build()); - copyRequestHeaders(req, proxyRequest, false); - } else { - if (StringUtils.isNotEmpty(req.getHeader(HttpHeaders.CONTENT_LENGTH)) || - StringUtils.isNotEmpty(req.getHeader(HttpHeaders.TRANSFER_ENCODING))) { - InputStreamEntity entity = new InputStreamEntity(req.getInputStream(), - Long.parseLong(req.getHeader(HttpHeaders.CONTENT_LENGTH))); - proxyRequest.setEntity(entity); - } - copyRequestHeaders(req, proxyRequest, true); - } - } - - /** - * Copy incoming request headers to the proxy request. - * - * @param req incoming {@link HttpServletRequest} - * @param httpRequest proxy request instance. - * @param preserveContentType true if content type header needs to be preserved. - * This should be set to false when handling multipart requests as Http - * client will generate the Content-Type header automatically. - */ - private void copyRequestHeaders(HttpServletRequest req, HttpRequestBase httpRequest, boolean preserveContentType) { - Enumeration headerNames = req.getHeaderNames(); - while (headerNames.hasMoreElements()) { - String headerName = headerNames.nextElement(); - if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH) || - headerName.equalsIgnoreCase(SM.COOKIE) || - (!preserveContentType && headerName.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE))) { - continue; - } - Enumeration headerValues = req.getHeaders(headerName); - while (headerValues.hasMoreElements()) { - httpRequest.setHeader(headerName, headerValues.nextElement()); - } - } - } - /*** * Validates the incoming request. * @@ -246,25 +170,10 @@ public class OTPInvokerHandler extends HttpServlet { if (StringUtils.isBlank(req.getHeader(HandlerConstants.OTP_HEADER))) { log.error("Unauthorized, Please provide OTP token."); - handleError(resp, HttpStatus.SC_UNAUTHORIZED); + HandlerUtil.handleError(resp, HttpStatus.SC_UNAUTHORIZED); return false; } return true; } - /** - * Handle error requests - * - * @param resp {@link HttpServletResponse} - * @param errorCode HTTP error status code - * @throws IOException If error occurred when trying to send the error response. - */ - private static void handleError(HttpServletResponse resp, int errorCode) - throws IOException { - ProxyResponse proxyResponse = new ProxyResponse(); - proxyResponse.setCode(errorCode); - proxyResponse.setExecutorResponse( - HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil.getStatusKey(errorCode)); - HandlerUtil.handleError(resp, proxyResponse); - } } diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/PermissionScopeHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/PermissionScopeHandler.java index 9ccb10c796..b5309492c7 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/PermissionScopeHandler.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/PermissionScopeHandler.java @@ -57,6 +57,7 @@ public class PermissionScopeHandler extends HttpServlet { JSONObject jsonObject = new JSONObject(); jsonObject.put(HandlerConstants.USER_SCOPES, authData.getScope()); proxyResponse.setCode(HttpStatus.SC_OK); + proxyResponse.setStatus(ProxyResponse.Status.SUCCESS); proxyResponse.setData(jsonObject.toString()); HandlerUtil.handleSuccess(resp, proxyResponse); return; 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 index d402f4795e..6fc5afa8ef 100644 --- 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 @@ -53,7 +53,7 @@ public class SsoLoginCallbackHandler extends HttpServlet { String iotsCoreUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_CORE_HOST_ENV_VAR) + HandlerConstants.COLON + HandlerUtil.getCorePort(req.getScheme()); String keyManagerUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_KM_HOST_ENV_VAR) - + HandlerConstants.COLON + HandlerUtil.getKeymanagerPort(req.getScheme()); + + HandlerConstants.COLON + HandlerUtil.getKeyManagerPort(req.getScheme()); if (session == null) { String baseContextPath = req.getContextPath(); 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 index f76f72a958..f6180c0a37 100644 --- 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 @@ -108,7 +108,7 @@ public class SsoLoginHandler extends HttpServlet { apiMgtUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_APIM_HOST_ENV_VAR) + HandlerConstants.COLON + HandlerUtil.getAPIManagerPort(req.getScheme()); keyManagerUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_KM_HOST_ENV_VAR) - + HandlerConstants.COLON + HandlerUtil.getKeymanagerPort(req.getScheme()); + + HandlerConstants.COLON + HandlerUtil.getKeyManagerPort(req.getScheme()); // Fetch ui config and persists in session String uiConfigUrl = iotsCoreUrl + HandlerConstants.UI_CONFIG_ENDPOINT; 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 index 92d14735ae..0e46e759f6 100644 --- 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 @@ -45,6 +45,7 @@ public class SsoLogoutHandler extends HttpServlet { } removeCookie(HandlerConstants.COMMON_AUTH_ID_KEY, "/", resp); ProxyResponse proxyResponse = new ProxyResponse(); + proxyResponse.setStatus(ProxyResponse.Status.SUCCESS); proxyResponse.setCode(HttpStatus.SC_OK); HttpSession session = req.getSession(false); diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/UserHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/UserHandler.java index 170030f399..4e1f120734 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/UserHandler.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/UserHandler.java @@ -57,7 +57,7 @@ public class UserHandler extends HttpServlet { String keymanagerUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_KM_HOST_ENV_VAR) - + HandlerConstants.COLON + HandlerUtil.getKeymanagerPort(req.getScheme()); + + HandlerConstants.COLON + HandlerUtil.getKeyManagerPort(req.getScheme()); HttpSession httpSession = req.getSession(false); if (httpSession == null) { HandlerUtil.sendUnAuthorizeResponse(resp); @@ -86,7 +86,11 @@ public class UserHandler extends HttpServlet { if (tokenStatus.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { if (tokenStatus.getCode() == HttpStatus.SC_UNAUTHORIZED) { - tokenStatus = HandlerUtil.retryRequestWithRefreshedToken(req, resp, tokenEndpoint, keymanagerUrl); + tokenStatus = HandlerUtil.retryRequestWithRefreshedToken(req, tokenEndpoint, keymanagerUrl); + if(!HandlerUtil.isResponseSuccessful(tokenStatus)) { + HandlerUtil.handleError(resp, tokenStatus); + return; + } } else { log.error("Error occurred while invoking the API to get token status."); HandlerUtil.handleError(resp, tokenStatus); @@ -108,6 +112,7 @@ public class UserHandler extends HttpServlet { return; } ProxyResponse proxyResponse = new ProxyResponse(); + proxyResponse.setStatus(ProxyResponse.Status.SUCCESS); proxyResponse.setCode(HttpStatus.SC_OK); proxyResponse.setData( jTokenResultAsJsonObject.get("username").getAsString().replaceAll("@carbon.super", "")); diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/beans/ProxyResponse.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/beans/ProxyResponse.java index ea1a550cfb..32c7ecb117 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/beans/ProxyResponse.java +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/beans/ProxyResponse.java @@ -17,11 +17,20 @@ */ package io.entgra.ui.request.interceptor.beans; +import org.apache.http.Header; + public class ProxyResponse { + public static class Status { + public static int SUCCESS = 1; + public static int ERROR = 0; + } + private int code; private String data; private String executorResponse; + private int status; + private Header[] headers; public int getCode() { return code; } @@ -34,4 +43,21 @@ public class ProxyResponse { public String getExecutorResponse() { return executorResponse; } public void setExecutorResponse(String executorResponse) { this.executorResponse = executorResponse; } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public Header[] getHeaders() { + return headers; + } + + public void setHeaders(Header[] headers) { + this.headers = headers; + } + } diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/GrafanaHandlerUtil.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/GrafanaHandlerUtil.java new file mode 100644 index 0000000000..3ba09f8d0d --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/util/GrafanaHandlerUtil.java @@ -0,0 +1,20 @@ +package io.entgra.ui.request.interceptor.util; + +import org.apache.commons.lang.StringUtils; + +import java.net.URI; + +public class GrafanaHandlerUtil { + public static String getGrafanaUri(URI req) { + StringBuilder grafanaUriBuilder = new StringBuilder(); + grafanaUriBuilder.append(req.getPath().replace(" ", "%20")); + if (StringUtils.isNotEmpty(req.getQuery())) { + grafanaUriBuilder.append("?").append(req.getQuery()); + } + return grafanaUriBuilder.toString(); + } + + public static String generateGrafanaUrl(URI request, String base) { + return base + GrafanaHandlerUtil.getGrafanaUri(request); + } +} 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 ac97931bf2..df0e085b0a 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 @@ -31,6 +31,11 @@ public class HandlerConstants { public static final String SSO_LOGIN_CALLBACK = "/ssoLoginCallback"; public static final String BASIC = "Basic "; public static final String BEARER = "Bearer "; + public static final String X_FRAME_OPTIONS = "X-Frame-Options"; + public static final String X_FRAME_OPTIONS_SAMEORIGIN = "SAMEORIGIN"; + public static final String UPGRADE = "Upgrade"; + public static final String WEB_SOCKET = "websocket"; + public static final String HTTP_UPGRADE = "HTTP/1.1"; public static final String TAGS_KEY = "tags"; public static final String APP_NAME_KEY = "applicationName"; public static final String SESSION_AUTH_DATA_KEY = "authInfo"; @@ -58,10 +63,13 @@ public class HandlerConstants { public static final String LOGIN_CACHE_CAPACITY_KEY = "loginCacheCapacity"; public static final String SCHEME_SEPARATOR = "://"; + public static final String URI_SEPARATOR = "/"; + public static final String QUERY_PARAM_KEY_VALUE_SEP = "="; public static final String COLON = ":"; public static final String HTTP_PROTOCOL = "http"; public static final String HTTPS_PROTOCOL = "https"; public static final String UNDERSCORE = "_"; + public static final String URI_QUERY_SEPARATOR = "?"; public static final int INTERNAL_ERROR_CODE = 500; public static final long TIMEOUT = 1200; @@ -71,9 +79,12 @@ public class HandlerConstants { public static final String AX_PREFIX = "ax2251:"; public static final String PAYLOADS_DIR = "repository/resources/payloads"; public static final String SOAP_ACTION_HEADER = "SOAPAction"; + public static final String REFERER_HEADER = "Referer"; public static final String WSS_PROTOCOL = "wss"; + public static final String WS_PROTOCOL = "ws"; public static final String REMOTE_SESSION_CONTEXT = "/remote/session/clients"; + public static final String GRAFANA_API = "/api/device-mgt/v1.0/reports/grafana"; public static final String IOT_CORE_HOST_ENV_VAR = "iot.core.host"; public static final String IOT_CORE_HTTP_PORT_ENV_VAR = "iot.core.http.port"; @@ -89,5 +100,6 @@ public class HandlerConstants { public static final String IOT_REMOTE_SESSION_HOST_ENV_VAR = "iot.remotesession.server.host"; public static final String IOT_REMOTE_SESSION_HTTPS_PORT_ENV_VAR = "iot.remotesession.server.https.port"; public static final String IOT_GW_HTTPS_PORT_ENV_VAR = "iot.gateway.https.port"; + public static final String IOT_REPORTING_WEBAPP_HOST_ENV_VAR = "iot.reporting.webapp.host"; public static final String USER_SCOPES = "userScopes"; } 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 48812430f4..64f8a3e221 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 @@ -25,19 +25,26 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import io.entgra.ui.request.interceptor.beans.AuthData; import io.entgra.ui.request.interceptor.cache.LoginCache; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; 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.HttpHeaders; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; +import org.apache.http.*; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.cookie.SM; import org.apache.http.entity.ContentType; +import org.apache.http.entity.InputStreamEntity; import org.apache.http.entity.StringEntity; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.mime.content.InputStreamBody; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.xml.serialize.OutputFormat; @@ -46,6 +53,7 @@ import org.json.JSONException; import org.json.JSONObject; import org.w3c.dom.Document; import io.entgra.ui.request.interceptor.beans.ProxyResponse; +import org.wso2.carbon.device.mgt.core.common.util.HttpUtil; import org.xml.sax.SAXException; import javax.servlet.http.HttpServletRequest; @@ -60,6 +68,8 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.Enumeration; +import java.util.List; public class HandlerUtil { @@ -83,54 +93,64 @@ public class HandlerUtil { log.error("Received null response for http request : " + httpRequest.getMethod() + " " + httpRequest .getURI().toString()); proxyResponse.setCode(HandlerConstants.INTERNAL_ERROR_CODE); + proxyResponse.setStatus(ProxyResponse.Status.ERROR); proxyResponse.setExecutorResponse(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + getStatusKey( HandlerConstants.INTERNAL_ERROR_CODE)); return proxyResponse; } else { int statusCode = response.getStatusLine().getStatusCode(); - try (BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))) { - StringBuilder result = new StringBuilder(); - String line; - while ((line = rd.readLine()) != null) { - result.append(line); - } - - String jsonString = result.toString(); - if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_CREATED) { + String jsonString = getResponseString(response); + if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_CREATED) { + proxyResponse.setCode(statusCode); + proxyResponse.setData(jsonString); + proxyResponse.setStatus(ProxyResponse.Status.SUCCESS); + proxyResponse.setExecutorResponse("SUCCESS"); + proxyResponse.setHeaders(response.getAllHeaders()); + return proxyResponse; + } else if (statusCode == HttpStatus.SC_UNAUTHORIZED) { + if (isTokenExpired(jsonString)) { + proxyResponse.setCode(statusCode); + proxyResponse.setStatus(ProxyResponse.Status.ERROR); + proxyResponse.setExecutorResponse(HandlerConstants.TOKEN_IS_EXPIRED); + } else { + log.error( + "Received " + statusCode + " response for http request : " + httpRequest.getMethod() + + " " + httpRequest.getURI().toString() + ". Error message: " + jsonString); proxyResponse.setCode(statusCode); proxyResponse.setData(jsonString); - proxyResponse.setExecutorResponse("SUCCESS"); - return proxyResponse; - } else if (statusCode == HttpStatus.SC_UNAUTHORIZED) { - if (jsonString.contains("Access token expired") || jsonString - .contains("Invalid input. Access token validation failed")) { - proxyResponse.setCode(statusCode); - proxyResponse.setExecutorResponse(HandlerConstants.TOKEN_IS_EXPIRED); - return proxyResponse; - } else { - log.error( - "Received " + statusCode + " response for http request : " + httpRequest.getMethod() - + " " + httpRequest.getURI().toString() + ". Error message: " + jsonString); - proxyResponse.setCode(statusCode); - proxyResponse.setData(jsonString); - proxyResponse.setExecutorResponse( - HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + getStatusKey(statusCode)); - return proxyResponse; - } + proxyResponse.setStatus(ProxyResponse.Status.ERROR); + proxyResponse.setExecutorResponse( + HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + getStatusKey(statusCode)); } - log.error("Received " + statusCode + - " response for http request : " + httpRequest.getMethod() + " " + httpRequest.getURI() - .toString() + ". Error message: " + jsonString); - proxyResponse.setCode(statusCode); - proxyResponse.setData(jsonString); - proxyResponse - .setExecutorResponse(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + getStatusKey(statusCode)); return proxyResponse; } + log.error("Received " + statusCode + + " response for http request : " + httpRequest.getMethod() + " " + httpRequest.getURI() + .toString() + ". Error message: " + jsonString); + proxyResponse.setCode(statusCode); + proxyResponse.setData(jsonString); + proxyResponse.setStatus(ProxyResponse.Status.ERROR); + proxyResponse + .setExecutorResponse(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + getStatusKey(statusCode)); + return proxyResponse; } } } + public static boolean isTokenExpired(String jsonBody) { + return jsonBody.contains("Access token expired") || jsonBody + .contains("Invalid input. Access token validation failed"); + } + + public static String getMemeType(HttpResponse response) { + String memeType = ""; + Header contentType = response.getEntity().getContentType(); + if (contentType != null) { + memeType = contentType.getValue().split(";")[0].trim(); + } + return memeType; + } + /*** * * @param statusCode Provide status code, e.g:- 400, 401, 500 etc @@ -172,6 +192,7 @@ public class HandlerUtil { } + /*** * Handle error requests. * @@ -183,6 +204,7 @@ public class HandlerUtil { Gson gson = new Gson(); if (proxyResponse == null) { proxyResponse = new ProxyResponse(); + proxyResponse.setStatus(ProxyResponse.Status.ERROR); proxyResponse.setCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); proxyResponse.setExecutorResponse(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil .getStatusKey(HandlerConstants.INTERNAL_ERROR_CODE)); @@ -200,17 +222,27 @@ public class HandlerUtil { /** * Handle error requests with custom error codes. * - * @param resp {@link HttpServletResponse} + * @param resp {@link HttpServletResponse} * @param errorCode HTTP error status code * @throws IOException If error occurred when trying to send the error response. */ public static void handleError(HttpServletResponse resp, int errorCode) throws IOException { + ProxyResponse proxyResponse = constructProxyResponseByErrorCode(errorCode); + HandlerUtil.handleError(resp, proxyResponse); + } + + public static ProxyResponse constructProxyResponseByErrorCode(int errorCode) { ProxyResponse proxyResponse = new ProxyResponse(); proxyResponse.setCode(errorCode); + proxyResponse.setStatus(ProxyResponse.Status.ERROR); proxyResponse.setExecutorResponse( HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil.getStatusKey(errorCode)); - HandlerUtil.handleError(resp, proxyResponse); + return proxyResponse; + } + + public static boolean isResponseSuccessful(ProxyResponse response) { + return response.getStatus() == ProxyResponse.Status.SUCCESS; } /*** @@ -265,12 +297,17 @@ public class HandlerUtil { * @param scheme https or https * @return {@link String} keymanager port */ - public static String getKeymanagerPort(String scheme) { - String keymanagerPort = System.getProperty(HandlerConstants.IOT_KM_HTTPS_PORT_ENV_VAR); + public static String getKeyManagerPort(String scheme) { + String keyManagerPort = System.getProperty(HandlerConstants.IOT_KM_HTTPS_PORT_ENV_VAR); if (HandlerConstants.HTTP_PROTOCOL.equals(scheme)) { - keymanagerPort = System.getProperty(HandlerConstants.IOT_KM_HTTP_PORT_ENV_VAR); + keyManagerPort = System.getProperty(HandlerConstants.IOT_KM_HTTP_PORT_ENV_VAR); } - return keymanagerPort; + return keyManagerPort; + } + + public static String getKeyManagerUrl(String scheme) { + return scheme + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_KM_HOST_ENV_VAR) + + HandlerConstants.COLON + HandlerUtil.getKeyManagerPort(scheme); } /** @@ -324,6 +361,7 @@ public class HandlerUtil { public static void sendUnAuthorizeResponse(HttpServletResponse resp) throws IOException { ProxyResponse proxyResponse = new ProxyResponse(); + proxyResponse.setStatus(ProxyResponse.Status.ERROR); proxyResponse.setCode(HttpStatus.SC_UNAUTHORIZED); proxyResponse.setExecutorResponse( HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil.getStatusKey(HttpStatus.SC_UNAUTHORIZED)); @@ -333,7 +371,7 @@ public class HandlerUtil { /** * Generates the target URL for the proxy request. * - * @param req incoming {@link HttpServletRequest} + * @param req incoming {@link HttpServletRequest} * @param apiEndpoint API Endpoint URL * @return Target URL */ @@ -347,6 +385,47 @@ public class HandlerUtil { return urlBuilder.toString(); } + public static String getIOTGatewayBase(HttpServletRequest req) { + return req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_GW_HOST_ENV_VAR) + + HandlerConstants.COLON + HandlerUtil.getGatewayPort(req.getScheme()); + } + + /** + * Generate te request entity for POST and PUT requests from the incoming request. + * + * @param req incoming {@link HttpServletRequest}. + * @param proxyRequest proxy request instance. + * @throws FileUploadException If unable to parse the incoming request for multipart content extraction. + * @throws IOException If error occurred while generating the request body. + */ + public static void generateRequestEntity(HttpServletRequest req, HttpEntityEnclosingRequestBase proxyRequest) + throws FileUploadException, IOException { + if (ServletFileUpload.isMultipartContent(req)) { + ServletFileUpload servletFileUpload = new ServletFileUpload(new DiskFileItemFactory()); + List fileItemList = servletFileUpload.parseRequest(req); + MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create(); + entityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + for (FileItem item : fileItemList) { + if (!item.isFormField()) { + entityBuilder.addPart(item.getFieldName(), new InputStreamBody(item.getInputStream(), + ContentType.create(item.getContentType()), item.getName())); + } else { + entityBuilder.addTextBody(item.getFieldName(), item.getString()); + } + } + proxyRequest.setEntity(entityBuilder.build()); + HandlerUtil.copyRequestHeaders(req, proxyRequest, false); + } else { + if (StringUtils.isNotEmpty(req.getHeader(HttpHeaders.CONTENT_LENGTH)) || + StringUtils.isNotEmpty(req.getHeader(HttpHeaders.TRANSFER_ENCODING))) { + InputStreamEntity entity = new InputStreamEntity(req.getInputStream(), + Long.parseLong(req.getHeader(HttpHeaders.CONTENT_LENGTH))); + proxyRequest.setEntity(entity); + } + HandlerUtil.copyRequestHeaders(req, proxyRequest, true); + } + } + /*** * Constructs the application registration payload for DCR. * @@ -376,7 +455,7 @@ public class HandlerUtil { * @return {@link JsonObject} of UI configurations */ public static JsonObject getUIConfigAndPersistInSession(String uiConfigUrl, String gatewayUrl, HttpSession httpSession, - HttpServletResponse resp) throws IOException { + HttpServletResponse resp) throws IOException { HttpGet uiConfigEndpoint = new HttpGet(uiConfigUrl); ProxyResponse uiConfigResponse = HandlerUtil.execute(uiConfigEndpoint); String executorResponse = uiConfigResponse.getExecutorResponse(); @@ -448,7 +527,7 @@ public class HandlerUtil { log.error("Error occurred while sending the response into the socket. ", e); } catch (ParserConfigurationException e) { log.error("Error while creating the document builder."); - } catch ( SAXException e) { + } catch (SAXException e) { log.error("Error while parsing xml file.", e); } @@ -496,56 +575,76 @@ public class HandlerUtil { /** * Retry request again after refreshing the access token * - * @param req incoming {@link HttpServletRequest} - * @param resp resp {@link HttpServletResponse} + * @param req incoming {@link HttpServletRequest} * @param httpRequest subclass of {@link HttpRequestBase} related to the current request. * @return {@link ProxyResponse} if successful and null if failed. * @throws IOException If an error occurs when try to retry the request. */ - public static ProxyResponse retryRequestWithRefreshedToken(HttpServletRequest req, HttpServletResponse resp, - HttpRequestBase httpRequest, String apiEndpoint) throws IOException { - if (refreshToken(req, resp, apiEndpoint)) { + public static ProxyResponse retryRequestWithRefreshedToken(HttpServletRequest req, HttpRequestBase httpRequest, + String apiEndpoint) throws IOException { + ProxyResponse retryResponse = refreshToken(req, apiEndpoint); + if (isResponseSuccessful(retryResponse)) { HttpSession session = req.getSession(false); if (session == null) { log.error("Unauthorized, You are not logged in. Please log in to the portal"); - handleError(resp, HttpStatus.SC_UNAUTHORIZED); - return null; + return constructProxyResponseByErrorCode(HttpStatus.SC_UNAUTHORIZED); } httpRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken()); ProxyResponse proxyResponse = HandlerUtil.execute(httpRequest); if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { log.error("Error occurred while invoking the API after refreshing the token."); - HandlerUtil.handleError(resp, proxyResponse); - return null; + return proxyResponse; } return proxyResponse; + } - return null; + return retryResponse; } /*** * This method is responsible to get the refresh token * * @param req {@link HttpServletRequest} - * @param resp {@link HttpServletResponse} * @return If successfully renew tokens, returns TRUE otherwise return FALSE * @throws IOException If an error occurs while witting error response to client side or invoke token renewal API */ - private static boolean refreshToken(HttpServletRequest req, HttpServletResponse resp, String keymanagerUrl) + private static ProxyResponse refreshToken(HttpServletRequest req, String keymanagerUrl) throws IOException { if (log.isDebugEnabled()) { log.debug("refreshing the token"); } - - HttpPost tokenEndpoint = new HttpPost(keymanagerUrl + HandlerConstants.TOKEN_ENDPOINT); + ProxyResponse tokenResultResponse; HttpSession session = req.getSession(false); if (session == null) { log.error("Couldn't find a session, hence it is required to login and proceed."); - handleError(resp, HttpStatus.SC_UNAUTHORIZED); - return false; + tokenResultResponse = constructProxyResponseByErrorCode(HttpStatus.SC_UNAUTHORIZED); +// handleError(resp, HttpStatus.SC_UNAUTHORIZED); + return tokenResultResponse; } authData = (AuthData) session.getAttribute(HandlerConstants.SESSION_AUTH_DATA_KEY); + tokenResultResponse = getTokenResult(authData, keymanagerUrl); + if (tokenResultResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { + log.error("Error occurred while refreshing access token."); +// HandlerUtil.handleError(resp, tokenResultResponse); + return tokenResultResponse; + } + + JsonParser jsonParser = new JsonParser(); + JsonElement jTokenResult = jsonParser.parse(tokenResultResponse.getData()); + + if (jTokenResult.isJsonObject()) { + setNewAuthData(constructAuthDataFromTokenResult(jTokenResult, authData), session); + return tokenResultResponse; + } + + log.error("Error Occurred in token renewal process."); + tokenResultResponse = constructProxyResponseByErrorCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); +// handleError(resp, HttpStatus.SC_INTERNAL_SERVER_ERROR); + return tokenResultResponse; + } + public static ProxyResponse getTokenResult(AuthData authData, String keymanagerUrl) throws IOException { + HttpPost tokenEndpoint = new HttpPost(keymanagerUrl + HandlerConstants.TOKEN_ENDPOINT); StringEntity tokenEndpointPayload = new StringEntity( "grant_type=refresh_token&refresh_token=" + authData.getRefreshToken() + "&scope=PRODUCTION", ContentType.APPLICATION_FORM_URLENCODED); @@ -555,35 +654,75 @@ public class HandlerUtil { tokenEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC + encodedClientApp); tokenEndpoint.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString()); + return HandlerUtil.execute(tokenEndpoint); + } - ProxyResponse tokenResultResponse = HandlerUtil.execute(tokenEndpoint); - if (tokenResultResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) { - log.error("Error occurred while refreshing access token."); - HandlerUtil.handleError(resp, tokenResultResponse); - return false; + public static void setNewAuthData(AuthData newAuthData, HttpSession session) { + authData = newAuthData; + session.setAttribute(HandlerConstants.SESSION_AUTH_DATA_KEY, newAuthData); + } + + public static AuthData constructAuthDataFromTokenResult(JsonElement tokenResult, AuthData authData) { + JsonObject jTokenResultAsJsonObject = tokenResult.getAsJsonObject(); + AuthData newAuthData = new AuthData(); + newAuthData.setAccessToken(jTokenResultAsJsonObject.get("access_token").getAsString()); + newAuthData.setRefreshToken(jTokenResultAsJsonObject.get("refresh_token").getAsString()); + newAuthData.setScope(jTokenResultAsJsonObject.get("scope").getAsString()); + newAuthData.setClientId(authData.getClientId()); + newAuthData.setClientSecret(authData.getClientSecret()); + newAuthData.setEncodedClientApp(authData.getEncodedClientApp()); + newAuthData.setUsername(authData.getUsername()); + return newAuthData; + } + + /** + * Copy incoming request headers to the proxy request. + * + * @param req incoming {@link HttpServletRequest} + * @param httpRequest proxy request instance. + * @param preserveContentType true if content type header needs to be preserved. + * This should be set to false when handling multipart requests as Http + * client will generate the Content-Type header automatically. + */ + public static void copyRequestHeaders(HttpServletRequest req, HttpRequestBase httpRequest, boolean preserveContentType) { + Enumeration headerNames = req.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH) || + headerName.equalsIgnoreCase(SM.COOKIE) || + (!preserveContentType && headerName.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE))) { + continue; + } + Enumeration headerValues = req.getHeaders(headerName); + while (headerValues.hasMoreElements()) { + httpRequest.setHeader(headerName, headerValues.nextElement()); + } } + } - JsonParser jsonParser = new JsonParser(); - JsonElement jTokenResult = jsonParser.parse(tokenResultResponse.getData()); + public static String getHeaderValue(String headerName, Header[] headers) { + String headerValue = null; + for(Header header : headers) { + if (header.getName().equalsIgnoreCase(headerName)) { + headerValue = header.getValue(); + } + } + return headerValue; + } - if (jTokenResult.isJsonObject()) { - JsonObject jTokenResultAsJsonObject = jTokenResult.getAsJsonObject(); - AuthData newAuthData = new AuthData(); - - newAuthData.setAccessToken(jTokenResultAsJsonObject.get("access_token").getAsString()); - newAuthData.setRefreshToken(jTokenResultAsJsonObject.get("refresh_token").getAsString()); - newAuthData.setScope(jTokenResultAsJsonObject.get("scope").getAsString()); - newAuthData.setClientId(authData.getClientId()); - newAuthData.setClientSecret(authData.getClientSecret()); - newAuthData.setEncodedClientApp(authData.getEncodedClientApp()); - newAuthData.setUsername(authData.getUsername()); - authData = newAuthData; - session.setAttribute(HandlerConstants.SESSION_AUTH_DATA_KEY, newAuthData); - return true; + public static String getResponseString(HttpResponse response) throws IOException { + try (BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))) { + StringBuilder responseBuilder = new StringBuilder(); + String line; + while ((line = rd.readLine()) != null) { + responseBuilder.append(line); + } + return responseBuilder.toString(); } + } - log.error("Error Occurred in token renewal process."); - handleError(resp, HttpStatus.SC_INTERNAL_SERVER_ERROR); - return false; + + public static boolean isPropertyDefined(String property) { + return StringUtils.isEmpty(System.getProperty(property)); } -} +} \ 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/websocket/GrafanaLiveSecurityFilter.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/websocket/GrafanaLiveSecurityFilter.java new file mode 100644 index 0000000000..6ce15b7b4f --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/websocket/GrafanaLiveSecurityFilter.java @@ -0,0 +1,47 @@ +package io.entgra.ui.request.interceptor.websocket; + +import io.entgra.ui.request.interceptor.beans.AuthData; +import io.entgra.ui.request.interceptor.util.HandlerConstants; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.io.IOException; + +public class GrafanaLiveSecurityFilter implements Filter { + + private static final Log log = LogFactory.getLog(GrafanaLiveSecurityFilter.class); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) servletRequest; + HttpSession session = req.getSession(false); + if (session == null) { + log.error("Unauthorized, You are not logged in. Please log in to the portal"); + return; + } + AuthData authData = (AuthData) session.getAttribute(HandlerConstants.SESSION_AUTH_DATA_KEY); + if (authData == null) { + log.error("Unauthorized, Access token not found in the current session"); + return; + } + filterChain.doFilter(servletRequest, servletResponse); + } + + @Override + public void destroy() { + + } +} diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/websocket/GrafanaWebSocketClient.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/websocket/GrafanaWebSocketClient.java new file mode 100644 index 0000000000..db921e7719 --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/websocket/GrafanaWebSocketClient.java @@ -0,0 +1,101 @@ +package io.entgra.ui.request.interceptor.websocket; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.websocket.ClientEndpoint; +import javax.websocket.ContainerProvider; +import javax.websocket.Session; +import javax.websocket.OnOpen; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.CloseReason; +import javax.websocket.WebSocketContainer; +import java.net.URI; +import io.entgra.ui.request.interceptor.util.HandlerConstants; +import org.wso2.carbon.device.mgt.common.exceptions.GrafanaManagementException; +import org.wso2.carbon.device.mgt.core.grafana.mgt.util.GrafanaUtil; + +import javax.websocket.ClientEndpointConfig; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; +import javax.ws.rs.core.HttpHeaders; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.function.Consumer; + +@ClientEndpoint +public class GrafanaWebSocketClient extends Endpoint { + + private static final Log log = LogFactory.getLog(GrafanaWebSocketClient.class); + private Session grafanaServerSession; + private Consumer messageConsumer; + + public GrafanaWebSocketClient(URI endpointURI) { + try { + ClientEndpointConfig clientEndpointConfig = handShakeRequestConfig(); + WebSocketContainer container = ContainerProvider.getWebSocketContainer(); + container.connectToServer(this, clientEndpointConfig, endpointURI); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public ClientEndpointConfig handShakeRequestConfig() { + ClientEndpointConfig.Configurator clientEndpointConfigConfigurator = new ClientEndpointConfig.Configurator() { + @Override + public void beforeRequest(Map> headers) { + try { + headers.put(HttpHeaders.AUTHORIZATION, + Collections.singletonList(GrafanaUtil.getBasicAuthBase64Header())); + } catch (GrafanaManagementException e) { + log.error(e); + } + } + + + }; + return ClientEndpointConfig.Builder.create(). + configurator(clientEndpointConfigConfigurator).build(); + } + + @OnOpen + @Override + public void onOpen(Session grafanaServerSession, EndpointConfig endpointConfig) { + // Due to a bug (https://bz.apache.org/bugzilla/show_bug.cgi?format=multiple&id=57788) + // in the tomcat version used, this has to coded like this + grafanaServerSession.addMessageHandler(String.class, message -> messageConsumer.accept(message)); + this.grafanaServerSession = grafanaServerSession; + } + + @OnClose + @Override + public void onClose(Session session, CloseReason reason) { + log.info("Server session closed: " + reason); + this.grafanaServerSession = null; + } + + @OnError + @Override + public void onError(Session session, Throwable t) { + log.error("Error occurred in grafana server session: " + t.toString()); + t.printStackTrace(); + } + + public void sendMessageToServer(String message) { + if (grafanaServerSession.getAsyncRemote() != null) { + grafanaServerSession.getAsyncRemote().sendText(message); + } + } + + public Session getGrafanaServerSession() { + return grafanaServerSession; + } + + public void addMessageConsumer(Consumer messageConsumer) { + this.messageConsumer = messageConsumer; + } +} diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/websocket/GrafanaWebSocketHandler.java b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/websocket/GrafanaWebSocketHandler.java new file mode 100644 index 0000000000..567489c9cd --- /dev/null +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/java/io/entgra/ui/request/interceptor/websocket/GrafanaWebSocketHandler.java @@ -0,0 +1,70 @@ +package io.entgra.ui.request.interceptor.websocket; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.core.grafana.mgt.exception.GrafanaEnvVariablesNotDefined; +import org.wso2.carbon.device.mgt.core.grafana.mgt.util.GrafanaConstants; +import org.wso2.carbon.device.mgt.core.grafana.mgt.util.GrafanaUtil; + +import javax.websocket.CloseReason; +import javax.websocket.server.ServerEndpoint; +import javax.websocket.Session; +import javax.websocket.OnOpen; +import javax.websocket.OnMessage; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +@ServerEndpoint(value = "/grafana/api/live/ws") +public class GrafanaWebSocketHandler { + private static final Log log = LogFactory.getLog(GrafanaWebSocketHandler.class); + private GrafanaWebSocketClient grafanaClient; + + @OnOpen + public void onOpen(Session browserSession) throws GrafanaEnvVariablesNotDefined { + URI grafanaUri = browserSession.getRequestURI(); + String grafanaWebSocketUrl = getGrafanaWebSocketUrl(grafanaUri); + try { + grafanaClient = new GrafanaWebSocketClient(new URI(grafanaWebSocketUrl)); + grafanaClient.addMessageConsumer(message -> sendMessageToBrowser(browserSession, message)); + } catch (URISyntaxException e) { + log.error("Invalid web socket uri provided", e); + } + } + + @OnClose + public void onClose(CloseReason reason) throws IOException { + log.info("Browser session closed: " + reason); + if (grafanaClient.getGrafanaServerSession() != null) { + grafanaClient.getGrafanaServerSession().close(); + } + } + + @OnMessage + public void onMessage(String message) { + grafanaClient.sendMessageToServer(message); + } + + @OnError + public void onError(Throwable t) { + log.error("Error occurred in grafana browser session: " + t.toString()); + t.printStackTrace(); + } + + public void sendMessageToBrowser(Session browserSession, String message) { + try { + // Avoid grafana client sending messages when browser session is already closed + if(browserSession.isOpen()) { + browserSession.getBasicRemote().sendText(message); + } + } catch (IOException e) { + log.error("Error occurred while sending message to browser", e); + } + } + + private String getGrafanaWebSocketUrl(URI requestUri) throws GrafanaEnvVariablesNotDefined { + return GrafanaUtil.getGrafanaWebSocketBase(requestUri.getScheme()) + GrafanaConstants.WS_LIVE_API; + } +} diff --git a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/webapp/WEB-INF/web.xml b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/webapp/WEB-INF/web.xml index f5ba0ae97a..32e9b34f98 100644 --- a/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/webapp/WEB-INF/web.xml +++ b/components/ui-request-interceptor/io.entgra.ui.request.interceptor/src/main/webapp/WEB-INF/web.xml @@ -88,6 +88,15 @@ Cache-Control: no-store, no-cache, must-revalidate, private + + GrafanaLiveSecurityFilter + io.entgra.ui.request.interceptor.websocket.GrafanaLiveSecurityFilter + + + + GrafanaLiveSecurityFilter + /grafana/api/live/ws + HttpHeaderSecurityFilter diff --git a/features/device-mgt/org.wso2.carbon.device.mgt.basics.feature/src/main/resources/conf/datasources/reporting-mgt-datasources.xml b/features/device-mgt/org.wso2.carbon.device.mgt.basics.feature/src/main/resources/conf/datasources/reporting-mgt-datasources.xml new file mode 100644 index 0000000000..c070808f0a --- /dev/null +++ b/features/device-mgt/org.wso2.carbon.device.mgt.basics.feature/src/main/resources/conf/datasources/reporting-mgt-datasources.xml @@ -0,0 +1,48 @@ + + + + + org.wso2.carbon.ndatasource.rdbms.RDBMSDataSourceReader + + + + + + jdbc/MDM_REPORTS_DS + The datasource used for Report Management + + jdbc/MDM_REPORTS_DS + + + + jdbc:mysql://localhost:3306/DM_DB?autoReconnect=true&relaxAutoCommit=true&useSSL=false + root + root + com.mysql.cj.jdbc.Driver + 50 + 60000 + true + SELECT 1 + 30000 + + + + + + diff --git a/features/device-mgt/org.wso2.carbon.device.mgt.basics.feature/src/main/resources/conf/grafana-config.xml b/features/device-mgt/org.wso2.carbon.device.mgt.basics.feature/src/main/resources/conf/grafana-config.xml new file mode 100644 index 0000000000..1954dfc2e4 --- /dev/null +++ b/features/device-mgt/org.wso2.carbon.device.mgt.basics.feature/src/main/resources/conf/grafana-config.xml @@ -0,0 +1,35 @@ + + + + + + + + 100 + + + 100 + + + + + admin + admin2 + + diff --git a/features/device-mgt/org.wso2.carbon.device.mgt.basics.feature/src/main/resources/conf/reporting-mgt.xml b/features/device-mgt/org.wso2.carbon.device.mgt.basics.feature/src/main/resources/conf/reporting-mgt.xml new file mode 100644 index 0000000000..b768822971 --- /dev/null +++ b/features/device-mgt/org.wso2.carbon.device.mgt.basics.feature/src/main/resources/conf/reporting-mgt.xml @@ -0,0 +1,21 @@ + + + + jdbc/MDM_REPORTS_DS + diff --git a/pom.xml b/pom.xml index 0677580c60..bb0f022ede 100644 --- a/pom.xml +++ b/pom.xml @@ -314,6 +314,16 @@ io.entgra.transport.mgt.sms.handler.common ${carbon.device.mgt.version} + + org.wso2.carbon.devicemgt + io.entgra.analytics.mgt.grafana.proxy.core + ${carbon.device.mgt.version} + + + org.wso2.carbon.devicemgt + io.entgra.analytics.mgt.grafana.proxy.common + ${carbon.device.mgt.version} + @@ -1259,6 +1269,11 @@ gson ${google.gson.version} + + com.google.guava + guava + ${google.guava.version} + com.squareup.okhttp3 okhttp @@ -2085,6 +2100,7 @@ 3.0.0.wso2v1 1.3 2.8.5 + 31.0.1-jre 4.6.0 1.13.0 9.3.1