Merge branch 'api/pr/grafana/device-mgt' into 'master'

Added grafana integration

See merge request entgra/carbon-device-mgt!829
feature/traccar-sync
Pahansith Gunathilake 3 years ago
commit 21e014d6a6

@ -0,0 +1,7 @@
package org.wso2.carbon.device.mgt.jaxrs.exception;
public class RefererNotValid extends Exception {
public RefererNotValid(String msg) {
super(msg);
}
}

@ -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(

@ -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

@ -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<String, List<String>> 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);
}
}
}
}
}

@ -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);
}
}

@ -338,6 +338,10 @@
<groupId>org.wso2.carbon.devicemgt</groupId>
<artifactId>io.entgra.server.bootup.heartbeat.beacon</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
</project>

@ -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<String, List<String>> queryMap = getQueryMap(request);
return queryMap.containsKey(param);
}
public static String getFirstQueryValue(String param, Map<String, List<String>> queryMap) {
List<String> valueList = queryMap.get(param);
String firstValue = null;
if(valueList != null) {
firstValue = valueList.get(0);
}
return firstValue;
}
public static Map<String, List<String>> getQueryMap(String uri) {
String query = getQueryFromURIPath(uri);
Map<String, List<String>> 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<String> valueList = new ArrayList<>();
map.put(name, valueList);
}
map.get(name).add(value);
}
}
}
return map;
}
public static Map<String, List<String>> getQueryMap(URI request) {
String query = request.getQuery();
Map<String, List<String>> 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<String> 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();
}
}

@ -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;
}
}

@ -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<CacheConfiguration> 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<CacheConfiguration> 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<CacheConfiguration> caches) {
this.caches = caches;
}
}

@ -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);
}
}
}

@ -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;
}
}

@ -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;
}
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -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;
}

@ -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;
}

@ -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;
}
}

@ -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<Integer, Datasource> datasourceAPICache;
private Cache<QueryTemplateCacheKey, String> queryTemplateAPICache;
private Cache<String, String> encodedQueryCache;
private CacheManager() {
initCache();
}
private static final class CacheManagerHolder {
static final CacheManager cacheManager = new CacheManager();
}
public static CacheManager getInstance() {
return CacheManagerHolder.cacheManager;
}
public Cache<String, String> getEncodedQueryCache() {
return encodedQueryCache;
}
public Cache<QueryTemplateCacheKey, String> getQueryTemplateAPICache() {
return queryTemplateAPICache;
}
public Cache<Integer, Datasource> 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 <K, V> Cache<K, V> buildDatasourceCache() {
return CacheBuilder.newBuilder().build();
}
private <K , V> Cache<K, V> 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);
}
}
}

@ -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;
}
}

@ -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;
}
}

@ -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);
}
}

@ -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();
}
}

@ -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<String> 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<String> 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 + "'";
}
}

@ -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<String> parameters;
public PreparedQuery(String preparedSQL, List<String> parameters) {
this.preparedSQL = preparedSQL;
this.parameters = parameters;
}
public String getPreparedSQL() {
return preparedSQL;
}
public void setPreparedSQL(String preparedSQL) {
this.preparedSQL = preparedSQL;
}
public List<String> getParameters() {
return parameters;
}
public void setParameters(List<String> parameters) {
this.parameters = parameters;
}
}

@ -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<String> parameters) throws SQLException {
List<String> 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<String> escapeParameters(List<String> parameters) {
List<String> escapedParams = new ArrayList<>();
for (String param : parameters) {
String escapedParam = StringEscapeUtils.escapeSql(param);
escapedParams.add(escapedParam);
}
return escapedParams;
}
}

@ -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<String> 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;
}
}

@ -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;
}

@ -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();
}
}
}

@ -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";
}

@ -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<String, List<String>> queryMap = HttpUtil.getQueryMap(iframeURL);
return HttpUtil.getFirstQueryValue(GrafanaConstants.PANEL_ID_QUERY_PARAM, queryMap);
}
public static String getOrgId(URI iframeURL) {
Map<String, List<String>> 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;
}
}

@ -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);

@ -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<Operation> operations = new ArrayList<Operation>();
List<Operation> 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<? extends Operation> getOperationsForDevice(int enrolmentId, PaginationRequest request)
throws OperationManagementDAOException {
Operation operation;
List<Operation> operations = new ArrayList<Operation>();
List<Operation> 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<String> operationCode = request.getOperationLogFilters().getOperationCode();
List<String> 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<Operation> 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);
}

@ -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 = ":";
}

@ -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;
}
}

@ -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;
}
}

@ -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<Connection> 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();
}
}

@ -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<String, List<String>> queryMap = getQueryMap(request);
return queryMap.containsKey(param);
}
public static String getFirstQueryValue(String param, Map<String, List<String>> queryMap) {
List<String> valueList = queryMap.get(param);
String firstValue = null;
if(valueList != null) {
firstValue = valueList.get(0);
}
return firstValue;
}
public static Map<String, List<String>> getQueryMap(String uri) {
String query = getQueryFromURIPath(uri);
Map<String, List<String>> 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<String> valueList = new ArrayList<>();
map.put(name, valueList);
}
map.get(name).add(value);
}
}
}
return map;
}
public static Map<String, List<String>> getQueryMap(URI request) {
String query = request.getQuery();
Map<String, List<String>> 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<String> 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);

@ -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;
}

@ -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);
}
}

@ -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<FileItem> 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 <code>true</code> if content type header needs to be preserved.
* This should be set to <code>false</code> when handling multipart requests as Http
* client will generate the Content-Type header automatically.
*/
private void copyRequestHeaders(HttpServletRequest req, HttpRequestBase httpRequest, boolean preserveContentType) {
Enumeration<String> 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<String> 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 <code>null</code> 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);
}
}

@ -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.";

@ -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);

@ -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<FileItem> 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 <code>true</code> if content type header needs to be preserved.
* This should be set to <code>false</code> when handling multipart requests as Http
* client will generate the Content-Type header automatically.
*/
private void copyRequestHeaders(HttpServletRequest req, HttpRequestBase httpRequest, boolean preserveContentType) {
Enumeration<String> 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<String> 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);
}
}

@ -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;

@ -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();

@ -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;

@ -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);

@ -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", ""));

@ -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;
}
}

@ -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);
}
}

@ -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";
}

@ -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<FileItem> 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 <code>null</code> 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 <code>true</code> if content type header needs to be preserved.
* This should be set to <code>false</code> 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<String> 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<String> 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));
}
}
}

@ -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() {
}
}

@ -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<String> 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<String, List<String>> 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<String> messageConsumer) {
this.messageConsumer = messageConsumer;
}
}

@ -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;
}
}

@ -88,6 +88,15 @@
<param-value>Cache-Control: no-store, no-cache, must-revalidate, private</param-value>
</init-param>
</filter>
<filter>
<filter-name>GrafanaLiveSecurityFilter</filter-name>
<filter-class>io.entgra.ui.request.interceptor.websocket.GrafanaLiveSecurityFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>GrafanaLiveSecurityFilter</filter-name>
<url-pattern>/grafana/api/live/ws</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>HttpHeaderSecurityFilter</filter-name>

@ -0,0 +1,48 @@
<!--
~ 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.
-->
<datasources-configuration xmlns:svns="http://org.wso2.securevault/configuration">
<providers>
<provider>org.wso2.carbon.ndatasource.rdbms.RDBMSDataSourceReader</provider>
</providers>
<datasources>
<datasource>
<name>jdbc/MDM_REPORTS_DS</name>
<description>The datasource used for Report Management</description>
<jndiConfig>
<name>jdbc/MDM_REPORTS_DS</name>
</jndiConfig>
<definition type="RDBMS">
<configuration>
<url>jdbc:mysql://localhost:3306/DM_DB?autoReconnect=true&amp;relaxAutoCommit=true&amp;useSSL=false</url>
<username>root</username>
<password>root</password>
<driverClassName>com.mysql.cj.jdbc.Driver</driverClassName>
<maxActive>50</maxActive>
<maxWait>60000</maxWait>
<testOnBorrow>true</testOnBorrow>
<validationQuery>SELECT 1</validationQuery>
<validationInterval>30000</validationInterval>
</configuration>
</definition>
</datasource>
</datasources>
</datasources-configuration>

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
~ 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.
-->
<GrafanaConfiguration>
<Cache >
<CacheConfiguration name="queryAPICache">
<Capacity>100</Capacity>
</CacheConfiguration>
<CacheConfiguration name="encodedQueryCache">
<Capacity>100</Capacity>
</CacheConfiguration>
</Cache>
<AdminUser>
<!-- properties can be listed and can be picked up in the relevant implementation class as required-->
<Username>admin</Username>
<Password>admin2</Password>
</AdminUser>
</GrafanaConfiguration>

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
~ 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.
-->
<ReportManagementConfiguration>
<DatasourceName>jdbc/MDM_REPORTS_DS</DatasourceName>
</ReportManagementConfiguration>

@ -314,6 +314,16 @@
<artifactId>io.entgra.transport.mgt.sms.handler.common</artifactId>
<version>${carbon.device.mgt.version}</version>
</dependency>
<dependency>
<groupId>org.wso2.carbon.devicemgt</groupId>
<artifactId>io.entgra.analytics.mgt.grafana.proxy.core</artifactId>
<version>${carbon.device.mgt.version}</version>
</dependency>
<dependency>
<groupId>org.wso2.carbon.devicemgt</groupId>
<artifactId>io.entgra.analytics.mgt.grafana.proxy.common</artifactId>
<version>${carbon.device.mgt.version}</version>
</dependency>
<!-- Device Management dependencies -->
<!-- Governance dependencies -->
@ -1259,6 +1269,11 @@
<artifactId>gson</artifactId>
<version>${google.gson.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${google.guava.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
@ -2085,6 +2100,7 @@
<commons-json.version>3.0.0.wso2v1</commons-json.version>
<json.smart.version>1.3</json.smart.version>
<google.gson.version>2.8.5</google.gson.version>
<google.guava.version>31.0.1-jre</google.guava.version>
<squareup.okhttp3.version>4.6.0</squareup.okhttp3.version>
<okio.version>1.13.0</okio.version>
<github.openfeign.version>9.3.1</github.openfeign.version>

Loading…
Cancel
Save