diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIConstants.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIConstants.java index 856731b01b82..30ca3663b531 100755 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIConstants.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/APIConstants.java @@ -864,6 +864,10 @@ private Permissions() { public static final String API_RESTAPI_ALLOWED_URI = API_RESTAPI + "AllowedURIs.AllowedURI."; public static final String API_RESTAPI_ALLOWED_URI_URI = API_RESTAPI_ALLOWED_URI + "URI"; public static final String API_RESTAPI_ALLOWED_URI_HTTPMethods = API_RESTAPI_ALLOWED_URI + "HTTPMethods"; + public static final String API_RESTAPI_BASIC_AUTH_BLOCKED_URI = API_RESTAPI + "BasicAuthBlockedURIs.BasicAuthBlockedURI."; + public static final String API_RESTAPI_BASIC_AUTH_BLOCKED_URI_URI = API_RESTAPI_BASIC_AUTH_BLOCKED_URI + "URI"; + public static final String API_RESTAPI_BASIC_AUTH_BLOCKED_URI_HTTPMethods = + API_RESTAPI_BASIC_AUTH_BLOCKED_URI + "HTTPMethods"; public static final String API_RESTAPI_ETAG_SKIP_LIST = API_RESTAPI + "ETagSkipList."; public static final String API_RESTAPI_ETAG_SKIP_URI = API_RESTAPI_ETAG_SKIP_LIST + "ETagSkipURI."; public static final String API_RESTAPI_ETAG_SKIP_URI_URI = API_RESTAPI_ETAG_SKIP_URI + "URI"; diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.util/src/main/java/org/wso2/carbon/apimgt/rest/api/util/interceptors/auth/BasicAuthenticationInterceptor.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.util/src/main/java/org/wso2/carbon/apimgt/rest/api/util/interceptors/auth/BasicAuthenticationInterceptor.java index 99aa067d3479..764658a9dc25 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.util/src/main/java/org/wso2/carbon/apimgt/rest/api/util/interceptors/auth/BasicAuthenticationInterceptor.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.util/src/main/java/org/wso2/carbon/apimgt/rest/api/util/interceptors/auth/BasicAuthenticationInterceptor.java @@ -26,8 +26,10 @@ import org.apache.cxf.message.Message; import org.apache.cxf.phase.AbstractPhaseInterceptor; import org.apache.cxf.phase.Phase; +import org.wso2.carbon.apimgt.api.APIManagementException; import org.wso2.carbon.apimgt.api.model.Scope; import org.wso2.carbon.apimgt.api.model.URITemplate; +import org.wso2.carbon.apimgt.impl.APIConstants; import org.wso2.carbon.apimgt.impl.utils.APIUtil; import org.wso2.carbon.apimgt.impl.utils.RealmUtil; import org.wso2.carbon.apimgt.rest.api.common.RestApiCommonUtil; @@ -46,6 +48,8 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Dictionary; +import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -87,6 +91,14 @@ public void handleMessage(Message inMessage) { if (policy != null) { inMessage.put(RestApiConstants.REQUEST_AUTHENTICATION_SCHEME, RestApiConstants.BASIC_AUTHENTICATION); //Extract user credentials from the auth header and validate. + String path = (String) inMessage.get(Message.PATH_INFO); + String httpMethod = (String) inMessage.get(Message.HTTP_REQUEST_METHOD); + if (isBasicAuthBlockedURI(path, httpMethod)) { + log.error("Requested URI:" + path + " with HTTP method: " + httpMethod + + " is not allowed with Basic Authentication"); + throw new AuthenticationException("Unauthenticated request"); + } + String username = StringUtils.trim(policy.getUserName()); String password = StringUtils.trim(policy.getPassword()); if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { @@ -306,4 +318,40 @@ private boolean validateUserRolesWithRESTAPIScopes(List resourceScopeList return false; } + /** + * This method will check if the requested URI is allowed to access with Basic Authentication + * + * @param path Requested URI path + * @param httpMethod HTTP Method + * @return true if the requested URI is not allowed with Basic Authentication + */ + private boolean isBasicAuthBlockedURI(String path, String httpMethod) { + Dictionary> blockedResourcePathsMap; + if (path.contains(APIConstants.RestApiConstants.REST_API_OLD_VERSION)) { + path = path.replace("/" + APIConstants.RestApiConstants.REST_API_OLD_VERSION, ""); + } + + //Check if the accessing URI is Basic Auth allowed and then authorization is failed if not. + try { + blockedResourcePathsMap = RestApiUtil.getBasicAuthBlockedURIsToMethodsMap(); + Enumeration uriTemplateSet = blockedResourcePathsMap.keys(); + + while (uriTemplateSet.hasMoreElements()) { + org.wso2.uri.template.URITemplate uriTemplate = uriTemplateSet.nextElement(); + if (uriTemplate.matches(path, new HashMap())) { + List blockedVerbs = blockedResourcePathsMap.get(uriTemplate); + if (blockedVerbs.contains(httpMethod)) { + return true; + } + } + } + + return false; + } catch (APIManagementException e) { + RestApiUtil + .handleInternalServerError("Unable to retrieve/process " + + "Basic Auth blocked URIs for REST API", e, log); + } + return false; + } } diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.util/src/main/java/org/wso2/carbon/apimgt/rest/api/util/utils/RestApiUtil.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.util/src/main/java/org/wso2/carbon/apimgt/rest/api/util/utils/RestApiUtil.java index 2ce4659f34f2..d0f025e61e94 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.util/src/main/java/org/wso2/carbon/apimgt/rest/api/util/utils/RestApiUtil.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.util/src/main/java/org/wso2/carbon/apimgt/rest/api/util/utils/RestApiUtil.java @@ -85,6 +85,7 @@ public class RestApiUtil { public static final Log log = LogFactory.getLog(RestApiUtil.class); private static Dictionary> uriToHttpMethodsMap; + private static Dictionary> basicAuthBlockedUriToHttpMethodsMap; private static Dictionary> ETagSkipListURIToHttpMethodsMap; public static ErrorDTO getConstraintViolationErrorDTO(Set> violations) { @@ -1094,6 +1095,67 @@ public static Dictionary> getAll return uriToHttpMethodsMap; } + /** + * Returns the Basic Auth Blocked URIs and associated HTTP methods for REST API + * by reading api-manager.xml configuration + * + * @return A Dictionary with the Basic Auth Blocked URIs and the associated HTTP methods. + * @throws APIManagementException + */ + private static Dictionary> getBasicAuthBlockedURIsMapFromConfig() + throws APIManagementException { + Dictionary> uriToMethodsMap = new Hashtable<>(); + APIManagerConfiguration apiManagerConfiguration = ServiceReferenceHolder.getInstance() + .getAPIManagerConfigurationService().getAPIManagerConfiguration(); + List uriList = apiManagerConfiguration + .getProperty(APIConstants.API_RESTAPI_BASIC_AUTH_BLOCKED_URI_URI); + List methodsList = apiManagerConfiguration + .getProperty(APIConstants.API_RESTAPI_BASIC_AUTH_BLOCKED_URI_HTTPMethods); + + if (uriList != null && methodsList != null) { + if (uriList.size() != methodsList.size()) { + String errorMsg = "Provided Basic Auth Blocked URIs for REST API are invalid." + + " Every 'BasicAuthAllowedURI' should include 'URI' and 'HTTPMethods' elements"; + log.error(errorMsg); + return new Hashtable<>(); + } + + for (int i = 0; i < uriList.size(); i++) { + String uri = uriList.get(i); + uri = uri.replace("/{version}", ""); + try { + org.wso2.uri.template.URITemplate uriTemplate = new org.wso2.uri.template.URITemplate(uri); + String methodsForUri = methodsList.get(i); + List methodListForUri = Arrays.asList(methodsForUri.split(",")); + uriToMethodsMap.put(uriTemplate, methodListForUri); + } catch (URITemplateException e) { + String msg = "Error in parsing URI " + uri + + " when retrieving Basic Auth Blocked URIs for REST API"; + log.error(msg, e); + throw new APIManagementException(msg, e); + } + } + } + return uriToMethodsMap; + } + + /** + * Returns the Basic Auth Blocked URIs and associated HTTP methods for REST API. If not already read before, reads + * api-manager.xml configuration, store the results in a static reference and returns the results. + * Otherwise, returns previously stored the static reference object. + * + * @return A Dictionary with the Basic Auth Allowed URIs and the associated HTTP methods. + * @throws APIManagementException + */ + public static Dictionary> getBasicAuthBlockedURIsToMethodsMap() + throws APIManagementException { + + if (basicAuthBlockedUriToHttpMethodsMap == null) { + basicAuthBlockedUriToHttpMethodsMap = getBasicAuthBlockedURIsMapFromConfig(); + } + return basicAuthBlockedUriToHttpMethodsMap; + } + /** * @param message CXF message to be extract auth header * @param pattern Pattern to extract access token diff --git a/features/apimgt/org.wso2.carbon.apimgt.core.feature/src/main/resources/conf_templates/templates/repository/conf/api-manager.xml.j2 b/features/apimgt/org.wso2.carbon.apimgt.core.feature/src/main/resources/conf_templates/templates/repository/conf/api-manager.xml.j2 index 86e7b7d51648..163f8356b66e 100644 --- a/features/apimgt/org.wso2.carbon.apimgt.core.feature/src/main/resources/conf_templates/templates/repository/conf/api-manager.xml.j2 +++ b/features/apimgt/org.wso2.carbon.apimgt.core.feature/src/main/resources/conf_templates/templates/repository/conf/api-manager.xml.j2 @@ -873,6 +873,17 @@ POST + + + {% if apim.rest_api.basic_auth_blocked_uri is defined %} + {% for uri in apim.rest_api.basic_auth_blocked_uri %} + + {{uri.uri_path}} + {{uri.http_method}} + + {% endfor %} + {% endif %} + /api/am/devportal/{version}/apis