diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 2a46abc9ce4..8b1f97b79cc 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -136,10 +136,6 @@ This endopint deals with users of the built-in authentication provider. Note tha For this service to work, the setting ``BuiltinUsers.KEY`` has to be set, and its value passed as ``key`` to each of the calls. -List all users:: - - GET http://$SERVER/api/users?key=$key - Generates a new user. Data about the user are posted via JSON. *Note that the password is passed as a parameter in the query*. :: POST http://$SERVER/api/users?password=$password&key=$key diff --git a/scripts/api/post-intall-api-block.sh b/scripts/api/post-intall-api-block.sh new file mode 100644 index 00000000000..eb03259baf9 --- /dev/null +++ b/scripts/api/post-intall-api-block.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# Run this script post-installation, to block all the settings that +# should not be available to the general public in a production Dataverse installation. + +curl -X PUT -d groups,s,index,datasetfield http://localhost:8080/api/s/settings/:BlockedApiEndpoints diff --git a/scripts/api/setup-all.sh b/scripts/api/setup-all.sh index 75e4f739afd..8fc83564ea7 100755 --- a/scripts/api/setup-all.sh +++ b/scripts/api/setup-all.sh @@ -60,3 +60,5 @@ echo # OPTIONAL USERS AND DATAVERSES #./setup-optional.sh + +echo "Setup done. Consider running post-install-api-block.sh for blocking the sensitive API." \ No newline at end of file diff --git a/scripts/api/testBlockEndpoints.sh b/scripts/api/testBlockEndpoints.sh new file mode 100755 index 00000000000..55a78519909 --- /dev/null +++ b/scripts/api/testBlockEndpoints.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +ADMIN_KEY=$1 + +echo Testing Groups +curl http://localhost:8080/api/groups/ip/?key=$ADMIN_KEY +echo + +echo blocking groups +curl -X PUT -d groups http://localhost:8080/api/s/settings/:BlockedApiEndpoints +echo + +echo Testing Groups again - expecting 503 Unavailable +curl -v http://localhost:8080/api/groups/ip/?key=$ADMIN_KEY +echo + +echo Unblocking groups +curl -X DELETE http://localhost:8080/api/s/settings/:BlockedApiEndpoints +echo + +echo Testing Groups +curl http://localhost:8080/api/groups/ip/?key=$ADMIN_KEY +echo + +echo blocking groups, Roles +curl -X PUT -d groups,roles http://localhost:8080/api/s/settings/:BlockedApiEndpoints +echo + +echo Testing Groups again - expecting 503 Unavailable +curl -v http://localhost:8080/api/groups/ip/?key=$ADMIN_KEY +echo + +echo Testing Roles - expecting 503 Unavailable +curl -v http://localhost:8080/api/roles/?key=$ADMIN_KEY +echo + +echo blocking Roles only +curl -X PUT -d roles http://localhost:8080/api/s/settings/:BlockedApiEndpoints +echo + +echo Testing Groups again +curl -v http://localhost:8080/api/groups/ip/?key=$ADMIN_KEY +echo + +echo Testing Roles - expecting 503 Unavailable +curl -v http://localhost:8080/api/roles/?key=$ADMIN_KEY +echo + +echo Unblocking all +curl -X DELETE http://localhost:8080/api/s/settings/:BlockedApiEndpoints +echo + +echo DONE diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java index ff911d6cf95..42b77f09800 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java @@ -8,8 +8,6 @@ import edu.harvard.iq.dataverse.authorization.providers.AuthenticationProviderRow; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.settings.Setting; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonObjectBuilder; diff --git a/src/main/java/edu/harvard/iq/dataverse/api/ApiBlockingFilter.java b/src/main/java/edu/harvard/iq/dataverse/api/ApiBlockingFilter.java new file mode 100644 index 00000000000..4d5f2de5c78 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/api/ApiBlockingFilter.java @@ -0,0 +1,97 @@ +package edu.harvard.iq.dataverse.api; + +import edu.harvard.iq.dataverse.settings.SettingsServiceBean; +import java.io.IOException; +import java.util.Set; +import java.util.TreeSet; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.ejb.EJB; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * A web filter to block API administration calls. + * @author michael + */ +@WebFilter( urlPatterns={"/api/*"} ) +public class ApiBlockingFilter implements javax.servlet.Filter { + + private static final Logger logger = Logger.getLogger(ApiBlockingFilter.class.getName()); + + @EJB + protected SettingsServiceBean settingsSvc; + + final Set blockedApiEndpoints = new TreeSet<>(); + private String lastEndpointList; + + @Override + public void init(FilterConfig fc) throws ServletException { + updateBlockedPoints(); + } + + private void updateBlockedPoints() { + blockedApiEndpoints.clear(); + String endpointList = settingsSvc.getValueForKey(SettingsServiceBean.Key.BlockedApiEndpoints, ""); + for ( String endpoint : endpointList.split(",") ) { + String endpointPrefix = canonize(endpoint); + if ( ! endpointPrefix.isEmpty() ) { + logger.log(Level.INFO, "Blocking API endpoint: {0}", endpointPrefix); + blockedApiEndpoints.add(endpointPrefix); + } + } + lastEndpointList = endpointList; + } + + @Override + public void doFilter(ServletRequest sr, ServletResponse sr1, FilterChain fc) throws IOException, ServletException { + String endpointList = settingsSvc.getValueForKey(SettingsServiceBean.Key.BlockedApiEndpoints, ""); + if ( ! endpointList.equals(lastEndpointList) ) { + updateBlockedPoints(); + } + + HttpServletRequest hsr = (HttpServletRequest) sr; + String apiEndpoint = canonize(hsr.getRequestURI().substring(hsr.getServletPath().length())); + + for ( String prefix : blockedApiEndpoints ) { + if ( apiEndpoint.startsWith(prefix) ) { + // Block! + HttpServletResponse httpResponse = (HttpServletResponse) sr1; + httpResponse.getWriter().println("{ status:\"error\", message:\"Endpoint blocked. Please contact the dataverse administrator\"}" ); + httpResponse.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + httpResponse.setContentType("application/json"); + return; + } + } + + fc.doFilter(sr, sr1); + } + + @Override + public void destroy() { + logger.info("WebFilter destroy"); + } + + /** + * Creates a canonical representation of {@code in}: trimmed spaces and slashes + * @param in the raw string + * @return {@code in} with no trailing and leading spaces and slashes. + */ + private String canonize( String in ) { + in = in.trim(); + if ( in.startsWith("/") ) { + in = in.substring(1); + } + if ( in.endsWith("/") ) { + in = in.substring(0, in.length()-1); + } + return in; + } + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Mail.java b/src/main/java/edu/harvard/iq/dataverse/api/Mail.java index 950919e3853..fadbc8f6e7f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Mail.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Mail.java @@ -1,8 +1,3 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package edu.harvard.iq.dataverse.api; import edu.harvard.iq.dataverse.MailServiceBean; diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java index 3ca86f7227c..98cb388e377 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java @@ -30,6 +30,12 @@ public enum Key { * Search API. See also https://github.com/IQSS/dataverse/issues/1299 */ SearchApiNonPublicAllowed, + + /** + * API endpoints that are not accessible. Comma separated list. + */ + BlockedApiEndpoints, + /** * For development only (see dev guide for details). Backed by an enum * of possible account types.