From d00817268e9385a9bc1d69d18dd60aa87b986250 Mon Sep 17 00:00:00 2001 From: amalhub Date: Wed, 7 Dec 2016 17:30:38 +0530 Subject: [PATCH] IOTS-292: Adding the api-mgt handler for gateway change --- .../org.wso2.carbon.apimgt.handlers/pom.xml | 42 +++ .../AuthenticationHandler.java | 227 ++++++++++++ .../invoker/RESTConstants.java | 26 ++ .../invoker/RESTInvoker.java | 343 ++++++++++++++++++ .../invoker/RESTResponse.java | 81 +++++ .../utils/AuthConstants.java | 35 ++ .../utils/CoreUtils.java | 140 +++++++ .../src/main/resources/api-filter-config.xml | 22 ++ components/apimgt-extensions/pom.xml | 1 + pom.xml | 4 + 10 files changed, 921 insertions(+) create mode 100644 components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/pom.xml create mode 100644 components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/AuthenticationHandler.java create mode 100644 components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/invoker/RESTConstants.java create mode 100644 components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/invoker/RESTInvoker.java create mode 100644 components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/invoker/RESTResponse.java create mode 100644 components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/utils/AuthConstants.java create mode 100644 components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/utils/CoreUtils.java create mode 100644 components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/resources/api-filter-config.xml diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/pom.xml b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/pom.xml new file mode 100644 index 00000000000..b8133bc6d81 --- /dev/null +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/pom.xml @@ -0,0 +1,42 @@ + + + + apimgt-extensions + org.wso2.carbon.devicemgt + 1.2.8-SNAPSHOT + + 4.0.0 + + org.wso2.carbon.apimgt.handlers + WSO2 Carbon - API Security Handler Component + + + + org.wso2.carbon + org.wso2.carbon.logging + ${carbon.kernel.version} + + + org.apache.synapse + synapse-core + ${org.apache.synapse.version} + + + org.apache.ws.security.wso2 + wss4j + ${org.apache.ws.security.wso2.version} + + + org.wso2.carbon.devicemgt + org.wso2.carbon.certificate.mgt.core + + + org.json.wso2 + json + ${commons-json.version} + + + + \ No newline at end of file diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/AuthenticationHandler.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/AuthenticationHandler.java new file mode 100644 index 00000000000..f5c5c800563 --- /dev/null +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/AuthenticationHandler.java @@ -0,0 +1,227 @@ +/* + * 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.apimgt.handlers; + +import org.apache.axiom.soap.SOAP11Constants; +import org.apache.axiom.soap.SOAP12Constants; +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.description.HandlerDescription; +import org.apache.axis2.description.Parameter; +import org.apache.axis2.engine.Handler; +import org.apache.axis2.namespace.Constants; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.ws.security.WSConstants; +import org.apache.ws.security.WSSecurityException; +import org.apache.ws.security.util.Base64; +import org.json.JSONObject; +import org.wso2.carbon.apimgt.handlers.invoker.RESTInvoker; +import org.wso2.carbon.apimgt.handlers.invoker.RESTResponse; +import org.wso2.carbon.apimgt.handlers.utils.AuthConstants; +import org.wso2.carbon.apimgt.handlers.utils.CoreUtils; + +import javax.xml.namespace.QName; +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AuthenticationHandler implements Handler { + private static final Log log = LogFactory.getLog(AuthenticationHandler.class); + private static HandlerDescription EMPTY_HANDLER_METADATA = new HandlerDescription("API Security Handler"); + private HandlerDescription handlerDesc; + private ArrayList apiList; + private RESTInvoker restInvoker; + + /** + * Setting up configurations at the constructor + */ + public AuthenticationHandler() { + log.info("Engaging API Security Handler"); + apiList = CoreUtils.readApiFilterList(); + restInvoker = new RESTInvoker(); + this.handlerDesc = EMPTY_HANDLER_METADATA; + } + + /** + * Handles incoming http/s requests + * + * @param messageContext + * @return response + * @throws AxisFault + */ + public InvocationResponse invoke(MessageContext messageContext) throws AxisFault { + boolean validateRequest = messageContext.getTo() != null; + + if (validateRequest && isSecuredAPI(messageContext)) { + String ctxPath = messageContext.getTo().getAddress().trim(); + CoreUtils.debugLog(log, "Authentication handler invoked by: ", ctxPath); + Map headers = (Map) messageContext.getProperty(MessageContext.TRANSPORT_HEADERS); + + if (headers.containsKey(AuthConstants.MDM_SIGNATURE)) { + String mdmSignature = headers.get(AuthConstants.MDM_SIGNATURE).toString(); + + try { + CoreUtils.debugLog(log, "Verify Cert:\n", mdmSignature); + + URI dcrUrl = new URI(AuthConstants.HTTPS + "://" + CoreUtils.getHost() + ":" + CoreUtils + .getHttpsPort() + "/dynamic-client-web/register"); + String dcrContent = "{\n" + + "\"owner\":\"" + CoreUtils.getUsername() + "\",\n" + + "\"clientName\":\"emm\",\n" + + "\"grantType\":\"refresh_token password client_credentials\",\n" + + "\"tokenScope\":\"default\"\n" + + "}"; + Map drcHeaders = new HashMap(); + drcHeaders.put("Content-Type", "application/json"); + + RESTResponse response = restInvoker.invokePOST(dcrUrl, drcHeaders, null, + null, dcrContent); + CoreUtils.debugLog(log, "DCR response:", response.getContent()); + JSONObject jsonResponse = new JSONObject(response.getContent()); + String clientId = jsonResponse.getString("client_id"); + String clientSecret = jsonResponse.getString("client_secret"); + + URI tokenUrl = new URI(AuthConstants.HTTPS + "://" + CoreUtils.getHost() + ":" + CoreUtils + .getHttpsPort() + "/oauth2/token"); + String tokenContent = "grant_type=password&username=" + CoreUtils.getUsername() + "&password=" + + CoreUtils.getPassword() + "&scope=activity-view"; + String tokenBasicAuth = "Basic " + Base64.encode((clientId + ":" + clientSecret).getBytes()); + Map tokenHeaders = new HashMap(); + tokenHeaders.put("Authorization", tokenBasicAuth); + tokenHeaders.put("Content-Type", "application/x-www-form-urlencoded"); + + response = restInvoker.invokePOST(tokenUrl, tokenHeaders, null, + null, tokenContent); + CoreUtils.debugLog(log, "Token response:", response.getContent()); + jsonResponse = new JSONObject(response.getContent()); + String accessToken = jsonResponse.getString("access_token"); + + URI certVerifyUrl = new URI(AuthConstants.HTTPS + "://" + CoreUtils.getHost() + ":" + CoreUtils + .getHttpsPort() + "/api/certificate-mgt/v1.0/admin/certificates/verify/ios"); + Map certVerifyHeaders = new HashMap(); + certVerifyHeaders.put("Authorization", "Bearer " + accessToken); + certVerifyHeaders.put("Content-Type", "application/json"); + String certVerifyContent = "{\n" + + "\"pem\":\"" + mdmSignature + "\",\n" + + "\"tenantId\": \"-1234\",\n" + + "\"serial\":\"\"\n" + + "}"; + + response = restInvoker.invokePOST(certVerifyUrl, certVerifyHeaders, null, + null, certVerifyContent); + CoreUtils.debugLog(log, "Verify response:", response.getContent()); + + if (!response.getContent().contains("invalid")) { + return InvocationResponse.CONTINUE; + } + log.warn("Unauthorized request for api: " + ctxPath); + setFaultCodeAndThrowAxisFault(messageContext, new Exception("Unauthorized!")); + return InvocationResponse.SUSPEND; + + } catch (Exception e) { + log.error("Error while processing certificate.", e); + setFaultCodeAndThrowAxisFault(messageContext, e); + return InvocationResponse.SUSPEND; + } + } else { + log.warn("Unauthorized request for api: " + ctxPath); + setFaultCodeAndThrowAxisFault(messageContext, new Exception("SSL required")); + return InvocationResponse.SUSPEND; + } + } else { + return InvocationResponse.CONTINUE; + } + + } + + /** + * API filter + * + * @param messageContext + * @return boolean + */ + private boolean isSecuredAPI(MessageContext messageContext) { + if (messageContext.getTransportIn() != null && + messageContext.getTransportIn().getName().toLowerCase().equals(AuthConstants.HTTPS)) { + for (String path : apiList) { + if (messageContext.getTo().getAddress().trim().contains(path)) { + return true; + } + } + } + return false; + } + + private void setFaultCodeAndThrowAxisFault(MessageContext msgContext, Exception e) throws AxisFault { + + msgContext.setProperty(AuthConstants.SEC_FAULT, Boolean.TRUE); + String soapVersionURI = msgContext.getEnvelope().getNamespace().getNamespaceURI(); + QName faultCode = null; + /* + * Get the faultCode from the thrown WSSecurity exception, if there is one + */ + if (e instanceof WSSecurityException) { + faultCode = ((WSSecurityException) e).getFaultCode(); + } + /* + * Otherwise default to InvalidSecurity + */ + if (faultCode == null) { + faultCode = new QName(WSConstants.INVALID_SECURITY.getNamespaceURI(), + WSConstants.INVALID_SECURITY.getLocalPart(), AuthConstants.WSSE); + } + + if (soapVersionURI.equals(SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI)) { + + throw new AxisFault(faultCode, e.getMessage(), e); + + } else if (soapVersionURI.equals(SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI)) { + + List subfaultCodes = new ArrayList(); + subfaultCodes.add(faultCode); + throw new AxisFault(Constants.FAULT_SOAP12_SENDER, subfaultCodes, e.getMessage(), e); + + } + + } + + public void cleanup() { + } + + public void init(HandlerDescription handlerDescription) { + this.handlerDesc = handlerDescription; + } + + public void flowComplete(MessageContext messageContext) { + } + + public HandlerDescription getHandlerDesc() { + return this.handlerDesc; + } + + public String getName() { + return "API security inflow handler"; + } + + public Parameter getParameter(String name) { + return this.handlerDesc.getParameter(name); + } +} diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/invoker/RESTConstants.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/invoker/RESTConstants.java new file mode 100644 index 00000000000..c1d0413a700 --- /dev/null +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/invoker/RESTConstants.java @@ -0,0 +1,26 @@ +/* + * 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.apimgt.handlers.invoker; + +public class RESTConstants { + static String REST_CLIENT_CONFIG_ELEMENT = "restClientConfiguration"; + static String REST_CLIENT_MAX_TOTAL_CONNECTIONS = "maxTotalConnections"; + static String REST_CLIENT_MAX_CONNECTIONS_PER_ROUTE = "maxConnectionsPerRoute"; + static String REST_CLEINT_CONNECTION_TIMEOUT = "connectionTimeout"; + static String REST_CLEINT_SOCKET_TIMEOUT = "socketTimeout"; +} diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/invoker/RESTInvoker.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/invoker/RESTInvoker.java new file mode 100644 index 00000000000..7873a7fc54e --- /dev/null +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/invoker/RESTInvoker.java @@ -0,0 +1,343 @@ +/* + * 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.apimgt.handlers.invoker; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.util.AXIOMUtil; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.Header; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.*; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.util.EntityUtils; +import org.wso2.carbon.apimgt.handlers.utils.AuthConstants; +import org.wso2.carbon.apimgt.handlers.utils.CoreUtils; +import org.wso2.carbon.utils.CarbonUtils; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Iterator; +import java.util.Map; + +public class RESTInvoker { + + private static final Log log = LogFactory.getLog(RESTInvoker.class); + + private int maxTotalConnections = 100; + private int maxTotalConnectionsPerRoute = 100; + private int connectionTimeout = 120000; + private int socketTimeout = 120000; + + private CloseableHttpClient client = null; + private PoolingHttpClientConnectionManager connectionManager = null; + + public RESTInvoker() { + configureHttpClient(); + } + + private void parseConfiguration() { + String carbonConfigDirPath = CarbonUtils.getCarbonConfigDirPath(); + String apiFilterConfigPath = carbonConfigDirPath + File.separator + + AuthConstants.AUTH_CONFIGURATION_FILE_NAME; + File configFile = new File(apiFilterConfigPath); + + try { + String configContent = FileUtils.readFileToString(configFile); + OMElement configElement = AXIOMUtil.stringToOM(configContent); + Iterator beans = configElement.getChildrenWithName( + new QName("http://www.springframework.org/schema/beans", "bean")); + + while (beans.hasNext()) { + OMElement bean = (OMElement) beans.next(); + String beanId = bean.getAttributeValue(new QName(null, "id")); + if (beanId.equals(RESTConstants.REST_CLIENT_CONFIG_ELEMENT)) { + Iterator beanProps = bean.getChildrenWithName( + new QName("http://www.springframework.org/schema/beans", "property")); + + while (beanProps.hasNext()) { + OMElement beanProp = (OMElement) beanProps.next(); + String beanName = beanProp.getAttributeValue(new QName(null, "name")); + if (RESTConstants.REST_CLIENT_MAX_TOTAL_CONNECTIONS.equals(beanName)) { + String value = beanProp.getAttributeValue(new QName(null, "value")); + if (value != null && !value.trim().equals("")) { + maxTotalConnections = Integer.parseInt(value); + } + CoreUtils.debugLog(log, "Max total http connections ", maxTotalConnections); + } else if (RESTConstants.REST_CLIENT_MAX_CONNECTIONS_PER_ROUTE.equals(beanName)) { + String value = beanProp.getAttributeValue(new QName(null, "value")); + if (value != null && !value.trim().equals("")) { + maxTotalConnectionsPerRoute = Integer.parseInt(value); + } + CoreUtils.debugLog(log, "Max total client connections per route ", maxTotalConnectionsPerRoute); + } else if (RESTConstants.REST_CLEINT_CONNECTION_TIMEOUT.equals(beanName)) { + String value = beanProp.getAttributeValue(new QName(null, "value")); + if (value != null && !value.trim().equals("")) { + connectionTimeout = Integer.parseInt(value); + } + } else if (RESTConstants.REST_CLEINT_SOCKET_TIMEOUT.equals(beanName)) { + String value = beanProp.getAttributeValue(new QName(null, "value")); + if (value != null && !value.trim().equals("")) { + socketTimeout = Integer.parseInt(value); + } + } + } + } + } + } catch (XMLStreamException e) { + log.error("Error in processing http connection settings, using default settings", e); + } catch (IOException e) { + log.error("Error in processing http connection settings, using default settings", e); + } + } + + private void configureHttpClient() { + + parseConfiguration(); + + RequestConfig defaultRequestConfig = RequestConfig.custom() + .setExpectContinueEnabled(true) + .setConnectTimeout(connectionTimeout) + .setSocketTimeout(socketTimeout) + .build(); + + connectionManager = new PoolingHttpClientConnectionManager(); + connectionManager.setDefaultMaxPerRoute(maxTotalConnectionsPerRoute); + connectionManager.setMaxTotal(maxTotalConnections); + client = HttpClients.custom() + .setConnectionManager(connectionManager) + .setDefaultRequestConfig(defaultRequestConfig) + .build(); + + CoreUtils.debugLog(log, "REST client initialized with ", + "maxTotalConnection = ", maxTotalConnections, + "maxConnectionsPerRoute = ", maxTotalConnectionsPerRoute, + "connectionTimeout = ", connectionTimeout); + } + + public void closeHttpClient() { + IOUtils.closeQuietly(client); + IOUtils.closeQuietly(connectionManager); + } + + /** + * Invokes the http GET method + * + * @param uri endpoint/service url + * @param requestHeaders header list + * @param username username for authentication + * @param password password for authentication + * @return RESTResponse of the GET request (can be the response body or the response status code) + * @throws Exception + */ + public RESTResponse invokeGET(URI uri, Map requestHeaders, String username, String password) throws IOException { + + HttpGet httpGet = null; + CloseableHttpResponse response = null; + Header[] headers; + int httpStatus; + String contentType; + String output; + try { + httpGet = new HttpGet(uri); + if (requestHeaders != null && !requestHeaders.isEmpty()) { + Object keys[] = requestHeaders.keySet().toArray(); + for (Object header : keys) { + httpGet.setHeader(header.toString(), requestHeaders.get(header).toString()); + } + } + response = sendReceiveRequest(httpGet, username, password); + output = IOUtils.toString(response.getEntity().getContent()); + headers = response.getAllHeaders(); + httpStatus = response.getStatusLine().getStatusCode(); + contentType = response.getEntity().getContentType().getValue(); + if (log.isTraceEnabled()) { + log.trace("Invoked GET " + uri.toString() + " - Response message: " + output); + } + EntityUtils.consume(response.getEntity()); + } finally { + if (response != null) { + IOUtils.closeQuietly(response); + } + if (httpGet != null) { + httpGet.releaseConnection(); + } + } + return new RESTResponse(contentType, output, headers, httpStatus); + } + + + public RESTResponse invokePOST(URI uri, Map requestHeaders, String username, + String password, String payload) throws IOException { + + HttpPost httpPost = null; + CloseableHttpResponse response = null; + Header[] headers; + int httpStatus; + String contentType; + String output; + try { + httpPost = new HttpPost(uri); + httpPost.setEntity(new StringEntity(payload)); + if (requestHeaders != null && !requestHeaders.isEmpty()) { + Object keys[] = requestHeaders.keySet().toArray(); + for (Object header : keys) { + httpPost.setHeader(header.toString(), requestHeaders.get(header).toString()); + } + } + response = sendReceiveRequest(httpPost, username, password); + output = IOUtils.toString(response.getEntity().getContent()); + headers = response.getAllHeaders(); + httpStatus = response.getStatusLine().getStatusCode(); + contentType = response.getEntity().getContentType().getValue(); + if (log.isTraceEnabled()) { + log.trace("Invoked POST " + uri.toString() + + " - Input payload: " + payload + " - Response message: " + output); + } + EntityUtils.consume(response.getEntity()); + } finally { + if (response != null) { + IOUtils.closeQuietly(response); + } + if (httpPost != null) { + httpPost.releaseConnection(); + } + } + return new RESTResponse(contentType, output, headers, httpStatus); + } + + /** + * Invokes the http PUT method + * + * @param uri endpoint/service url + * @param requestHeaders header list + * @param username username for authentication + * @param password password for authentication + * @param payload payload body passed + * @return RESTResponse of the PUT request (can be the response body or the response status code) + * @throws Exception + */ + public RESTResponse invokePUT(URI uri, Map requestHeaders, String username, String password, + String payload) throws IOException { + + HttpPut httpPut = null; + CloseableHttpResponse response = null; + Header[] headers; + int httpStatus; + String contentType; + String output; + try { + httpPut = new HttpPut(uri); + httpPut.setEntity(new StringEntity(payload)); + if (requestHeaders != null && !requestHeaders.isEmpty()) { + Object keys[] = requestHeaders.keySet().toArray(); + for (Object header : keys) { + httpPut.setHeader(header.toString(), requestHeaders.get(header).toString()); + } + } + response = sendReceiveRequest(httpPut, username, password); + output = IOUtils.toString(response.getEntity().getContent()); + headers = response.getAllHeaders(); + httpStatus = response.getStatusLine().getStatusCode(); + contentType = response.getEntity().getContentType().getValue(); + if (log.isTraceEnabled()) { + log.trace("Invoked PUT " + uri.toString() + " - Response message: " + output); + } + EntityUtils.consume(response.getEntity()); + } finally { + if (response != null) { + IOUtils.closeQuietly(response); + } + if (httpPut != null) { + httpPut.releaseConnection(); + } + } + return new RESTResponse(contentType, output, headers, httpStatus); + } + + /** + * Invokes the http DELETE method + * + * @param uri endpoint/service url + * @param requestHeaders header list + * @param username username for authentication + * @param password password for authentication + * @return RESTResponse of the DELETE (can be the response status code or the response body) + * @throws Exception + */ + public RESTResponse invokeDELETE(URI uri, Map requestHeaders, String username, String password) throws IOException { + + HttpDelete httpDelete = null; + CloseableHttpResponse response = null; + Header[] headers; + int httpStatus; + String contentType; + String output; + try { + httpDelete = new HttpDelete(uri); + if (requestHeaders != null && !requestHeaders.isEmpty()) { + Object keys[] = requestHeaders.keySet().toArray(); + for (Object header : keys) { + httpDelete.setHeader(header.toString(), requestHeaders.get(header).toString()); + } + } + response = sendReceiveRequest(httpDelete, username, password); + output = IOUtils.toString(response.getEntity().getContent()); + headers = response.getAllHeaders(); + httpStatus = response.getStatusLine().getStatusCode(); + contentType = response.getEntity().getContentType().getValue(); + if (log.isTraceEnabled()) { + log.trace("Invoked DELETE " + uri.toString() + " - Response message: " + output); + } + EntityUtils.consume(response.getEntity()); + } finally { + if (response != null) { + IOUtils.closeQuietly(response); + } + if (httpDelete != null) { + httpDelete.releaseConnection(); + } + } + return new RESTResponse(contentType, output, headers, httpStatus); + } + + private CloseableHttpResponse sendReceiveRequest(HttpRequestBase requestBase, String username, String password) + throws IOException { + CloseableHttpResponse response; + if (username != null && !username.equals("") && password != null) { + String combinedCredentials = username + ":" + password; + byte[] encodedCredentials = Base64.encodeBase64(combinedCredentials.getBytes(StandardCharsets.UTF_8)); + requestBase.addHeader("Authorization", "Basic " + new String(encodedCredentials)); + + response = client.execute(requestBase); + } else { + response = client.execute(requestBase); + } + return response; + } +} diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/invoker/RESTResponse.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/invoker/RESTResponse.java new file mode 100644 index 00000000000..7ce0389baac --- /dev/null +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/invoker/RESTResponse.java @@ -0,0 +1,81 @@ +/* + * 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.apimgt.handlers.invoker; + +import org.apache.http.Header; + +/** + * RESTResponse class holds the data retrieved from the HTTP invoke response. + */ +public class RESTResponse { + private String contentType; + private String content; + private Header[] headers; + private int httpStatus; + + /** + * Constructor + * + * @param contentType from the REST invoke response + * @param content from the REST invoke response + * @param headers from the REST invoke response + * @param httpStatus from the REST invoke response + */ + public RESTResponse(String contentType, String content, Header[] headers, int httpStatus) { + this.contentType = contentType; + this.content = content; + this.headers = headers; + this.httpStatus = httpStatus; + } + + /** + * Get the content type of the EST invoke response + * + * @return String content type of the response + */ + public String getContentType() { + return contentType; + } + + /** + * Get contents of the REST invoke response + * + * @return contents of the REST invoke response + */ + public String getContent() { + return content; + } + + /** + * Get headers of the REST invoke response + * + * @return headers of the REST invoke response + */ + public Header[] getHeaders() { + return headers; + } + + /** + * Get the HTTP Status code from REST invoke response + * + * @return int HTTP status code + */ + public int getHttpStatus() { + return httpStatus; + } +} diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/utils/AuthConstants.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/utils/AuthConstants.java new file mode 100644 index 00000000000..b7c9a00dfae --- /dev/null +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/utils/AuthConstants.java @@ -0,0 +1,35 @@ +/* + * 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.apimgt.handlers.utils; + +public class AuthConstants { + public static final String SEC_FAULT = "SECURITY_VALIDATION_FAILURE"; + public static final String HTTPS = "https"; + public static final String WSSE = "wsse"; + public static final String SSL_CERT_X509 = "ssl.client.auth.cert.X509"; + public static final String AUTH_CONFIGURATION_FILE_NAME = "api-filter-config.xml"; + public static final String API_FILTER_CONFIG_ELEMENT = "apiFilterConfig"; + public static final String API_LIST_PROPERTY = "apiList"; + public static final String HOST = "host"; + public static final String HTTPS_PORT = "httpsPort"; + public static final String USERNAME = "username"; + public static final String PASSWORD = "password"; + public static final String MDM_SIGNATURE = "mdm-signature"; + public static final String IOS = "ios"; + public static final String ANDROID = "android"; +} diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/utils/CoreUtils.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/utils/CoreUtils.java new file mode 100644 index 00000000000..42a7fe9ea75 --- /dev/null +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/java/org.wso2.carbon.apimgt.handlers/utils/CoreUtils.java @@ -0,0 +1,140 @@ +/* + * 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.apimgt.handlers.utils; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.util.AXIOMUtil; +import org.apache.commons.io.FileUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.utils.CarbonUtils; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; + +public class CoreUtils { + private static final Log log = LogFactory.getLog(CoreUtils.class); + private static String host = "localhost"; + private static int httpsPort = 9443; + private static String username = "admin"; + private static String password = "admin"; + + /** + * Reading configurations from api-filter-config.xml file + * + * @return ArrayList of api contexts + */ + public static ArrayList readApiFilterList() { + ArrayList apiList = new ArrayList(); + String carbonConfigDirPath = CarbonUtils.getCarbonConfigDirPath(); + String apiFilterConfigPath = carbonConfigDirPath + File.separator + + AuthConstants.AUTH_CONFIGURATION_FILE_NAME; + File configFile = new File(apiFilterConfigPath); + + try { + String configContent = FileUtils.readFileToString(configFile); + OMElement configElement = AXIOMUtil.stringToOM(configContent); + Iterator beans = configElement.getChildrenWithName( + new QName("http://www.springframework.org/schema/beans", "bean")); + + while (beans.hasNext()) { + OMElement bean = (OMElement) beans.next(); + String beanId = bean.getAttributeValue(new QName(null, "id")); + if (beanId.equals(AuthConstants.API_FILTER_CONFIG_ELEMENT)) { + Iterator beanProps = bean.getChildrenWithName( + new QName("http://www.springframework.org/schema/beans", "property")); + + while (beanProps.hasNext()) { + OMElement beanProp = (OMElement) beanProps.next(); + String beanName = beanProp.getAttributeValue(new QName(null, "name")); + if (AuthConstants.API_LIST_PROPERTY.equals(beanName)) { + Iterator apiListSet = ((OMElement) beanProp.getChildrenWithLocalName("set").next()) + .getChildrenWithLocalName("value"); + while (apiListSet.hasNext()) { + String apiContext = ((OMElement) apiListSet.next()).getText(); + apiList.add(apiContext); + CoreUtils.debugLog(log, "Adding security to api: ", apiContext); + } + } else if (AuthConstants.HOST.equals(beanName)) { + String value = beanProp.getAttributeValue(new QName(null, "value")); + host = value; + } else if (AuthConstants.HTTPS_PORT.equals(beanName)) { + String value = beanProp.getAttributeValue(new QName(null, "value")); + if (value != null && !value.trim().equals("")) { + httpsPort = Integer.parseInt(value); + } + } else if (AuthConstants.USERNAME.equals(beanName)) { + String value = beanProp.getAttributeValue(new QName(null, "value")); + username = value; + } else if (AuthConstants.PASSWORD.equals(beanName)) { + String value = beanProp.getAttributeValue(new QName(null, "value")); + password = value; + } + } + } + } + } catch (IOException e) { + log.error("Error in reading api filter settings", e); + } catch (XMLStreamException e) { + log.error("Error in reading api filter settings", e); + } + return apiList; + } + + /** + * Universal debug log function + * + * @param logger Log object specific to the class + * @param message initial debug log message + * @param vars optional strings to be appended for the log + */ + public static void debugLog(Log logger, String message, Object ... vars) { + if(logger.isDebugEnabled()) { + if (vars.length < 1) { + logger.debug(message); + return; + } + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(message); + for (Object var : vars) { + stringBuilder.append(var.toString()); + } + logger.debug(stringBuilder.toString()); + } + } + + public static String getHost() { + return host; + } + + public static int getHttpsPort() { + return httpsPort; + } + + public static String getUsername() { + return username; + } + + public static String getPassword() { + return password; + } +} diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/resources/api-filter-config.xml b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/resources/api-filter-config.xml new file mode 100644 index 00000000000..8811ccb8e78 --- /dev/null +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.handlers/src/main/resources/api-filter-config.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + /services/echo + /abc + + + + + + + + \ No newline at end of file diff --git a/components/apimgt-extensions/pom.xml b/components/apimgt-extensions/pom.xml index 4086cf5a18e..8d486740eeb 100644 --- a/components/apimgt-extensions/pom.xml +++ b/components/apimgt-extensions/pom.xml @@ -38,6 +38,7 @@ org.wso2.carbon.apimgt.application.extension org.wso2.carbon.apimgt.application.extension.api org.wso2.carbon.apimgt.annotations + org.wso2.carbon.apimgt.handlers diff --git a/pom.xml b/pom.xml index ccb5bf93d3b..eeabd51d554 100644 --- a/pom.xml +++ b/pom.xml @@ -1930,6 +1930,10 @@ 1.6.1 + + + 2.1.7-wso2v7 + 1.5.11.wso2v15