Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Basic Auth Blocked URI configuration to RestAPIs #12715

Merged
merged 1 commit into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -306,4 +318,40 @@ private boolean validateUserRolesWithRESTAPIScopes(List<Scope> 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<org.wso2.uri.template.URITemplate,List<String>> 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<org.wso2.uri.template.URITemplate> uriTemplateSet = blockedResourcePathsMap.keys();

while (uriTemplateSet.hasMoreElements()) {
org.wso2.uri.template.URITemplate uriTemplate = uriTemplateSet.nextElement();
if (uriTemplate.matches(path, new HashMap<String, String>())) {
List<String> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public class RestApiUtil {

public static final Log log = LogFactory.getLog(RestApiUtil.class);
private static Dictionary<org.wso2.uri.template.URITemplate, List<String>> uriToHttpMethodsMap;
private static Dictionary<org.wso2.uri.template.URITemplate, List<String>> basicAuthBlockedUriToHttpMethodsMap;
private static Dictionary<org.wso2.uri.template.URITemplate, List<String>> ETagSkipListURIToHttpMethodsMap;

public static <T> ErrorDTO getConstraintViolationErrorDTO(Set<ConstraintViolation<T>> violations) {
Expand Down Expand Up @@ -1094,6 +1095,67 @@ public static Dictionary<org.wso2.uri.template.URITemplate, List<String>> 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<org.wso2.uri.template.URITemplate, List<String>> getBasicAuthBlockedURIsMapFromConfig()
throws APIManagementException {
Dictionary<org.wso2.uri.template.URITemplate, List<String>> uriToMethodsMap = new Hashtable<>();
APIManagerConfiguration apiManagerConfiguration = ServiceReferenceHolder.getInstance()
.getAPIManagerConfigurationService().getAPIManagerConfiguration();
List<String> uriList = apiManagerConfiguration
.getProperty(APIConstants.API_RESTAPI_BASIC_AUTH_BLOCKED_URI_URI);
List<String> 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<String> 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<org.wso2.uri.template.URITemplate, List<String>> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,17 @@
<HTTPMethods>POST</HTTPMethods>
</AllowedURI>
</AllowedURIs>
<BasicAuthBlockedURIs>
<!--Configure Basic Auth blocked URIs of REST API. URIs included cannot be invoked with basic auth-->
{% if apim.rest_api.basic_auth_blocked_uri is defined %}
{% for uri in apim.rest_api.basic_auth_blocked_uri %}
<BasicAuthBlockedURI>
<URI>{{uri.uri_path}}</URI>
<HTTPMethods>{{uri.http_method}}</HTTPMethods>
</BasicAuthBlockedURI>
{% endfor %}
{% endif %}
</BasicAuthBlockedURIs>
<ETagSkipList>
<ETagSkipURI>
<URI>/api/am/devportal/{version}/apis</URI>
Expand Down
Loading