From 23f82f1867928d6222ef311f345a74761d756b51 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 18 Apr 2023 13:48:28 +0100 Subject: [PATCH 1/4] Added: CORS headers and frontend URL MicroProfile property for API session auth feature flag --- .../source/installation/config.rst | 2 +- .../iq/dataverse/api/ApiBlockingFilter.java | 17 ++++++++++++----- .../iq/dataverse/settings/FeatureFlags.java | 1 + .../iq/dataverse/settings/JvmSettings.java | 6 +++++- .../META-INF/microprofile-config.properties | 4 ++++ 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index 9828d5bb6e4..c6754305d96 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -2368,7 +2368,7 @@ please find all known feature flags below. Any of these flags can be activated u - Description - Default status * - api-session-auth - - Enables API authentication via session cookie (JSESSIONID). **Caution: Enabling this feature flag exposes the installation to CSRF risks!** We expect this feature flag to be temporary (only used by frontend developers, see `#9063 `_) and removed once support for bearer tokens has been implemented (see `#9229 `_). + - Enables API authentication via session cookie (JSESSIONID). **Caution: Enabling this feature flag exposes the installation to CSRF risks!** We expect this feature flag to be temporary (only used by frontend developers, see `#9063 `_) and removed once support for bearer tokens has been implemented (see `#9229 `_). For this feature flag to work, a frontend URL must be specified, which is required for the CORS configuration of the API. This can be done via the MicroProfile Config variable "dataverse.frontend.url". - ``Off`` **Note:** Feature flags can be set via any `supported MicroProfile Config API source`_, e.g. the environment variable diff --git a/src/main/java/edu/harvard/iq/dataverse/api/ApiBlockingFilter.java b/src/main/java/edu/harvard/iq/dataverse/api/ApiBlockingFilter.java index 6bf852d25f7..b2c66b218d4 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/ApiBlockingFilter.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/ApiBlockingFilter.java @@ -2,12 +2,11 @@ import edu.harvard.iq.dataverse.authorization.groups.impl.ipaddress.ip.IpAddress; import edu.harvard.iq.dataverse.engine.command.DataverseRequest; +import edu.harvard.iq.dataverse.settings.FeatureFlags; +import edu.harvard.iq.dataverse.settings.JvmSettings; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import java.io.IOException; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import javax.ejb.EJB; @@ -161,7 +160,15 @@ public void doFilter(ServletRequest sr, ServletResponse sr1, FilterChain fc) thr } try { if (settingsSvc.isTrueForKey(SettingsServiceBean.Key.AllowCors, true )) { - ((HttpServletResponse) sr1).addHeader("Access-Control-Allow-Origin", "*"); + String accessControlAllowOriginHeader = "Access-Control-Allow-Origin"; + if (FeatureFlags.API_SESSION_AUTH.enabled()) { + String frontendOrigin = JvmSettings.FRONTEND_URL.lookup(); + ((HttpServletResponse) sr1).addHeader(accessControlAllowOriginHeader, frontendOrigin); + ((HttpServletResponse) sr1).addHeader("Access-Control-Allow-Credentials", "true"); + } + else { + ((HttpServletResponse) sr1).addHeader(accessControlAllowOriginHeader, "*"); + } ((HttpServletResponse) sr1).addHeader("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS"); ((HttpServletResponse) sr1).addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, X-Dataverse-Key, Range"); ((HttpServletResponse) sr1).addHeader("Access-Control-Expose-Headers", "Accept-Ranges, Content-Range, Content-Encoding"); diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/FeatureFlags.java b/src/main/java/edu/harvard/iq/dataverse/settings/FeatureFlags.java index 6b27b380e22..0cdfd9743a6 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/FeatureFlags.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/FeatureFlags.java @@ -26,6 +26,7 @@ public enum FeatureFlags { /** * Enables API authentication via session cookie (JSESSIONID). Caution: Enabling this feature flag exposes the installation to CSRF risks + * For this feature flag to work, a frontend URL must be specified, which is required for the CORS configuration of the API. This can be done via the MicroProfile Config variable "dataverse.frontend.url". * @apiNote Raise flag by setting "dataverse.feature.api-session-auth" * @since Dataverse 5.14 */ diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java b/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java index 86130f5146e..7f840e4cf6f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java @@ -105,7 +105,11 @@ public enum JvmSettings { SCOPE_PID_HANDLENET_KEY(SCOPE_PID_HANDLENET, "key"), HANDLENET_KEY_PATH(SCOPE_PID_HANDLENET_KEY, "path", "dataverse.handlenet.admcredfile"), HANDLENET_KEY_PASSPHRASE(SCOPE_PID_HANDLENET_KEY, "passphrase", "dataverse.handlenet.admprivphrase"), - + + // FRONTEND + SCOPE_FRONTEND(PREFIX, "frontend"), + FRONTEND_URL(SCOPE_FRONTEND, "url"), + ; private static final String SCOPE_SEPARATOR = "."; diff --git a/src/main/resources/META-INF/microprofile-config.properties b/src/main/resources/META-INF/microprofile-config.properties index 3e166d0527f..d2acf96a650 100644 --- a/src/main/resources/META-INF/microprofile-config.properties +++ b/src/main/resources/META-INF/microprofile-config.properties @@ -44,6 +44,10 @@ dataverse.oai.server.maxsets=100 # can be customized via the setting below: #dataverse.oai.server.repositoryname= +# FRONTEND +# The frontend URL is required when enabling API session auth feature flag (dataverse.feature.api-session-auth=true) +# dataverse.frontend.url= + # PERSISTENT IDENTIFIER PROVIDERS # EZID dataverse.pid.ezid.api-url=https://ezid.cdlib.org From 7dfedb52ec73214e1a5d6461e50ac965358900b3 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 21 Apr 2023 11:57:42 +0100 Subject: [PATCH 2/4] Added: logout endpoint --- .../edu/harvard/iq/dataverse/api/Logout.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/main/java/edu/harvard/iq/dataverse/api/Logout.java diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Logout.java b/src/main/java/edu/harvard/iq/dataverse/api/Logout.java new file mode 100644 index 00000000000..d6d8d5cdc44 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/api/Logout.java @@ -0,0 +1,41 @@ +package edu.harvard.iq.dataverse.api; + +import edu.harvard.iq.dataverse.DataverseHeaderFragment; +import edu.harvard.iq.dataverse.DataverseSession; +import edu.harvard.iq.dataverse.settings.FeatureFlags; + +import javax.inject.Inject; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; + +@Path("logout") +public class Logout extends AbstractApiBean { + + @Inject + DataverseSession session; + + /** + * The only current API authentication mechanism subject to Log Out is the session cookie auth, and this mechanism is only available when the corresponding feature flag is enabled: + * + * @see FeatureFlags#API_SESSION_AUTH + *

+ * This endpoint replicates the logic from the JSF Log Out feature: + * @see DataverseHeaderFragment#logout() + *

+ * TODO: This endpoint must change when a final API authentication mechanism is established for use cases / applications subject to Log Out + */ + @POST + @Path("/") + public Response logout() { + if (!FeatureFlags.API_SESSION_AUTH.enabled()) { + return error(Response.Status.INTERNAL_SERVER_ERROR, "This endpoint is only available when session authentication feature flag is enabled"); + } + if (!session.getUser().isAuthenticated()) { + return error(Response.Status.BAD_REQUEST, "No valid session cookie was sent in the request"); + } + session.setUser(null); + session.setStatusDismissed(false); + return ok("User logged out"); + } +} From 10a1e365a6a45a6641262789a72fde7813541b91 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 21 Apr 2023 15:03:46 +0100 Subject: [PATCH 3/4] Added: basic Logout IT --- .../harvard/iq/dataverse/api/LogoutIT.java | 24 +++++++++++++++++++ .../edu/harvard/iq/dataverse/api/UtilIT.java | 7 ++++++ 2 files changed, 31 insertions(+) create mode 100644 src/test/java/edu/harvard/iq/dataverse/api/LogoutIT.java diff --git a/src/test/java/edu/harvard/iq/dataverse/api/LogoutIT.java b/src/test/java/edu/harvard/iq/dataverse/api/LogoutIT.java new file mode 100644 index 00000000000..f07ce970914 --- /dev/null +++ b/src/test/java/edu/harvard/iq/dataverse/api/LogoutIT.java @@ -0,0 +1,24 @@ +package edu.harvard.iq.dataverse.api; + +import com.jayway.restassured.RestAssured; +import com.jayway.restassured.response.Response; +import org.junit.BeforeClass; +import org.junit.jupiter.api.Test; + +import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class LogoutIT { + + @BeforeClass + public static void setUpClass() { + RestAssured.baseURI = UtilIT.getRestAssuredBaseUri(); + } + + @Test + public void testLogout() { + // Test failure because feature flag is turned off + Response logoutResponse = UtilIT.logout(); + assertEquals(INTERNAL_SERVER_ERROR.getStatusCode(), logoutResponse.getStatusCode()); + } +} diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index 642480cf11c..cded7bcd050 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -3170,4 +3170,11 @@ static String getSignedUrlFromResponse(Response createSignedUrlResponse) { String signedUrl = jsonPath.getString("data.signedUrl"); return signedUrl; } + + static Response logout() { + Response response = given() + .contentType("application/json") + .post("/api/logout"); + return response; + } } From 3b40665dcbd24358f8f58c8d534e90e151f2d47e Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 26 Apr 2023 17:54:59 +0100 Subject: [PATCH 4/4] Removed: CORS changes to revert to original --- .../source/installation/config.rst | 2 +- .../iq/dataverse/api/ApiBlockingFilter.java | 17 +++++------------ .../iq/dataverse/settings/FeatureFlags.java | 1 - .../iq/dataverse/settings/JvmSettings.java | 4 ---- .../META-INF/microprofile-config.properties | 4 ---- 5 files changed, 6 insertions(+), 22 deletions(-) diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index c6754305d96..9828d5bb6e4 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -2368,7 +2368,7 @@ please find all known feature flags below. Any of these flags can be activated u - Description - Default status * - api-session-auth - - Enables API authentication via session cookie (JSESSIONID). **Caution: Enabling this feature flag exposes the installation to CSRF risks!** We expect this feature flag to be temporary (only used by frontend developers, see `#9063 `_) and removed once support for bearer tokens has been implemented (see `#9229 `_). For this feature flag to work, a frontend URL must be specified, which is required for the CORS configuration of the API. This can be done via the MicroProfile Config variable "dataverse.frontend.url". + - Enables API authentication via session cookie (JSESSIONID). **Caution: Enabling this feature flag exposes the installation to CSRF risks!** We expect this feature flag to be temporary (only used by frontend developers, see `#9063 `_) and removed once support for bearer tokens has been implemented (see `#9229 `_). - ``Off`` **Note:** Feature flags can be set via any `supported MicroProfile Config API source`_, e.g. the environment variable diff --git a/src/main/java/edu/harvard/iq/dataverse/api/ApiBlockingFilter.java b/src/main/java/edu/harvard/iq/dataverse/api/ApiBlockingFilter.java index b2c66b218d4..6bf852d25f7 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/ApiBlockingFilter.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/ApiBlockingFilter.java @@ -2,11 +2,12 @@ import edu.harvard.iq.dataverse.authorization.groups.impl.ipaddress.ip.IpAddress; import edu.harvard.iq.dataverse.engine.command.DataverseRequest; -import edu.harvard.iq.dataverse.settings.FeatureFlags; -import edu.harvard.iq.dataverse.settings.JvmSettings; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import java.io.IOException; -import java.util.*; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import javax.ejb.EJB; @@ -160,15 +161,7 @@ public void doFilter(ServletRequest sr, ServletResponse sr1, FilterChain fc) thr } try { if (settingsSvc.isTrueForKey(SettingsServiceBean.Key.AllowCors, true )) { - String accessControlAllowOriginHeader = "Access-Control-Allow-Origin"; - if (FeatureFlags.API_SESSION_AUTH.enabled()) { - String frontendOrigin = JvmSettings.FRONTEND_URL.lookup(); - ((HttpServletResponse) sr1).addHeader(accessControlAllowOriginHeader, frontendOrigin); - ((HttpServletResponse) sr1).addHeader("Access-Control-Allow-Credentials", "true"); - } - else { - ((HttpServletResponse) sr1).addHeader(accessControlAllowOriginHeader, "*"); - } + ((HttpServletResponse) sr1).addHeader("Access-Control-Allow-Origin", "*"); ((HttpServletResponse) sr1).addHeader("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS"); ((HttpServletResponse) sr1).addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, X-Dataverse-Key, Range"); ((HttpServletResponse) sr1).addHeader("Access-Control-Expose-Headers", "Accept-Ranges, Content-Range, Content-Encoding"); diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/FeatureFlags.java b/src/main/java/edu/harvard/iq/dataverse/settings/FeatureFlags.java index 0cdfd9743a6..6b27b380e22 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/FeatureFlags.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/FeatureFlags.java @@ -26,7 +26,6 @@ public enum FeatureFlags { /** * Enables API authentication via session cookie (JSESSIONID). Caution: Enabling this feature flag exposes the installation to CSRF risks - * For this feature flag to work, a frontend URL must be specified, which is required for the CORS configuration of the API. This can be done via the MicroProfile Config variable "dataverse.frontend.url". * @apiNote Raise flag by setting "dataverse.feature.api-session-auth" * @since Dataverse 5.14 */ diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java b/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java index 7f840e4cf6f..480f076eb40 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java @@ -106,10 +106,6 @@ public enum JvmSettings { HANDLENET_KEY_PATH(SCOPE_PID_HANDLENET_KEY, "path", "dataverse.handlenet.admcredfile"), HANDLENET_KEY_PASSPHRASE(SCOPE_PID_HANDLENET_KEY, "passphrase", "dataverse.handlenet.admprivphrase"), - // FRONTEND - SCOPE_FRONTEND(PREFIX, "frontend"), - FRONTEND_URL(SCOPE_FRONTEND, "url"), - ; private static final String SCOPE_SEPARATOR = "."; diff --git a/src/main/resources/META-INF/microprofile-config.properties b/src/main/resources/META-INF/microprofile-config.properties index d2acf96a650..3e166d0527f 100644 --- a/src/main/resources/META-INF/microprofile-config.properties +++ b/src/main/resources/META-INF/microprofile-config.properties @@ -44,10 +44,6 @@ dataverse.oai.server.maxsets=100 # can be customized via the setting below: #dataverse.oai.server.repositoryname= -# FRONTEND -# The frontend URL is required when enabling API session auth feature flag (dataverse.feature.api-session-auth=true) -# dataverse.frontend.url= - # PERSISTENT IDENTIFIER PROVIDERS # EZID dataverse.pid.ezid.api-url=https://ezid.cdlib.org