From 0f724e913489ae067166eda46b99b6f7c9216135 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Tue, 21 Jul 2026 17:07:16 -0400 Subject: [PATCH 1/5] new setting to determine create behavior w.r.t. default license assignment --- doc/sphinx-guides/source/api/changelog.rst | 2 + .../source/installation/config.rst | 14 +++ .../iq/dataverse/settings/JvmSettings.java | 4 +- .../iq/dataverse/util/json/JsonParser.java | 99 +++++++++++-------- .../dataverse/util/json/JsonParserTest.java | 49 ++++++++- 5 files changed, 123 insertions(+), 45 deletions(-) diff --git a/doc/sphinx-guides/source/api/changelog.rst b/doc/sphinx-guides/source/api/changelog.rst index 10e368da51f..874df2b2ed1 100644 --- a/doc/sphinx-guides/source/api/changelog.rst +++ b/doc/sphinx-guides/source/api/changelog.rst @@ -15,6 +15,8 @@ v6.12 - **/api/admin/index/perms/{id}** +- Dataset creation API calls may now behave differently when neither a license nor terms are provided, depending on the new :ref:`dataverse.api.assume-default-license-when-not-provided-via-api` setting. + v6.11 ----- - The GET /api/mydata/retrieve, if the search returns no data, now includes the "data" block with 0 results. The message that was returned in "error_message" will be returned in "message" and the "success" will be `true`. All other errors will continue to reply with "success":false and the error message in "error_message". diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index 13eed47b6c4..6ff6a4d7f95 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -3367,6 +3367,20 @@ Defaults to ``false``. Can also be set via any `supported MicroProfile Config API source`_, e.g. the environment variable ``DATAVERSE_API_ALLOW_INCOMPLETE_METADATA``. Will accept ``[tT][rR][uU][eE]|1|[oO][nN]`` as "true" expressions. +.. _dataverse.api.assume-default-license-when-not-provided-via-api: + +dataverse.api.assume-default-license-when-not-provided-via-api +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +When creating a dataset via API, if neither a license nor any terms of use are provided, this setting determines whether the default license is assigned. + +- If set to ``true``, the default license is assigned if no license and no terms are provided. +- If set to ``false``, no license is assigned (and no terms). +- If not set (default), the behavior depends on the :ref:`:AllowCustomTermsOfUse` setting: if custom terms are allowed, no license is assigned; if not allowed, the default license is assigned. + +Can also be set via any `supported MicroProfile Config API source`_, e.g. the environment variable +``DATAVERSE_API_ASSUME_DEFAULT_LICENSE_WHEN_NOT_PROVIDED_VIA_API``. Will accept ``[tT][rR][uU][eE]|1|[oO][nN]`` as "true" expressions. + .. _dataverse.api.blocked.endpoints: dataverse.api.blocked.endpoints 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 ed0d796a84a..cbcc42e1041 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java @@ -96,7 +96,7 @@ public enum JvmSettings { // API: MDC Citation updates SCOPE_API_MDC(SCOPE_API, "mdc"), API_MDC_UPDATE_MIN_DELAY_MS(SCOPE_API_MDC, "min-delay-ms"), - + ASSUME_DEFAULT_LICENSE_WHEN_NOT_PROVIDED_VIA_API(SCOPE_API, "assume-default-license-when-not-provided-via-api"), // SIGNPOSTING SETTINGS SCOPE_SIGNPOSTING(PREFIX, "signposting"), @@ -318,7 +318,7 @@ public enum JvmSettings { SCOPE_COARNOTIFY(SCOPE_LINKEDDATANOTIFICATION, "coar-notify"), SCOPE_COARNOTIFY_RELATIONSHIP_ANNOUNCEMENT(SCOPE_COARNOTIFY, "relationship-announcement"), COARNOTIFY_RELATIONSHIP_ANNOUNCEMENT_NOTIFY_SUPERUSERS_ONLY(SCOPE_COARNOTIFY_RELATIONSHIP_ANNOUNCEMENT, "notify-superusers-only"), - ; + ; private static final String SCOPE_SEPARATOR = "."; public static final String PLACEHOLDER_KEY = "%s"; diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java index 7f3db716456..1fde24f3852 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java @@ -17,6 +17,7 @@ import edu.harvard.iq.dataverse.license.License; import edu.harvard.iq.dataverse.license.LicenseServiceBean; import edu.harvard.iq.dataverse.settings.FeatureFlags; +import edu.harvard.iq.dataverse.settings.JvmSettings; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import edu.harvard.iq.dataverse.util.BundleUtil; import edu.harvard.iq.dataverse.util.StringUtil; @@ -470,25 +471,27 @@ public DatasetVersion parseDatasetVersion(JsonObject obj, DatasetVersion dsv) th License license = null; - try { - // This method will attempt to parse the license in the format - // in which it appears in our json exports, as a compound - // field, for ex.: - // "license": { - // "name": "CC0 1.0", - // "uri": "http://creativecommons.org/publicdomain/zero/1.0" - // } - license = parseLicense(obj.getJsonObject("license")); - } catch (ClassCastException cce) { - logger.fine("class cast exception parsing the license section (will try parsing as a string)"); - // attempt to parse as string: - // i.e. this is for backward compatibility, after the bug in #9155 - // was fixed, with the old style of encoding the license info - // in input json, for ex.: - // "license" : "CC0 1.0" - license = parseLicense(obj.getString("license", null)); + if (obj.containsKey("license")) { + try { + // This method will attempt to parse the license in the format + // in which it appears in our json exports, as a compound + // field, for ex.: + // "license": { + // "name": "CC0 1.0", + // "uri": "http://creativecommons.org/publicdomain/zero/1.0" + // } + license = parseLicense(obj.getJsonObject("license")); + } catch (ClassCastException cce) { + logger.fine("class cast exception parsing the license section (will try parsing as a string)"); + // attempt to parse as string: + // i.e. this is for backward compatibility, after the bug in #9155 + // was fixed, with the old style of encoding the license info + // in input json, for ex.: + // "license" : "CC0 1.0" + license = parseLicense(obj.getString("license", null)); + } } - + //test to see if license exists in dataset type //if not set it to null - //only test if Dataset has a type and if it has custom available licenses @@ -505,21 +508,43 @@ public DatasetVersion parseDatasetVersion(JsonObject obj, DatasetVersion dsv) th license = null; } } - } + } + + terms.setTermsOfUse(obj.getString("termsOfUse", null)); + terms.setConfidentialityDeclaration(obj.getString("confidentialityDeclaration", null)); + terms.setSpecialPermissions(obj.getString("specialPermissions", null)); + terms.setRestrictions(obj.getString("restrictions", null)); + terms.setCitationRequirements(obj.getString("citationRequirements", null)); + terms.setDepositorRequirements(obj.getString("depositorRequirements", null)); + terms.setConditions(obj.getString("conditions", null)); + terms.setDisclaimer(obj.getString("disclaimer", null)); if (license == null) { - terms.setLicense(license); - terms.setTermsOfUse(obj.getString("termsOfUse", null)); - terms.setConfidentialityDeclaration(obj.getString("confidentialityDeclaration", null)); - terms.setSpecialPermissions(obj.getString("specialPermissions", null)); - terms.setRestrictions(obj.getString("restrictions", null)); - terms.setCitationRequirements(obj.getString("citationRequirements", null)); - terms.setDepositorRequirements(obj.getString("depositorRequirements", null)); - terms.setConditions(obj.getString("conditions", null)); - terms.setDisclaimer(obj.getString("disclaimer", null)); - } else { - terms.setLicense(license); + // If no license was provided or the provided license was invalid, + // we check if terms were provided. + boolean termsProvided = terms.getTermsOfUse() != null + || terms.getConfidentialityDeclaration() != null + || terms.getSpecialPermissions() != null + || terms.getRestrictions() != null + || terms.getCitationRequirements() != null + || terms.getDepositorRequirements() != null + || terms.getConditions() != null + || terms.getDisclaimer() != null; + + Optional assumeDefault = JvmSettings.ASSUME_DEFAULT_LICENSE_WHEN_NOT_PROVIDED_VIA_API.lookupOptional(Boolean.class); + + if (assumeDefault.isPresent()) { + if (assumeDefault.get() && !termsProvided) { + license = licenseService.getDefault(); + } + } else { + // Default behavior (when setting not set) + if (!settingsService.isTrueForKey(SettingsServiceBean.Key.AllowCustomTermsOfUse, true)) { + license = licenseService.getDefault(); + } + } } + terms.setLicense(license); terms.setTermsOfAccess(obj.getString("termsOfAccess", null)); terms.setDataAccessPlace(obj.getString("dataAccessPlace", null)); terms.setOriginalArchive(obj.getString("originalArchive", null)); @@ -700,12 +725,7 @@ public GuestbookResponse parseGuestbookResponse(JsonObject obj, GuestbookRespons private edu.harvard.iq.dataverse.license.License parseLicense(String licenseNameOrUri) throws JsonParseException { if (licenseNameOrUri == null){ - boolean safeDefaultIfKeyNotFound = true; - if (settingsService.isTrueForKey(SettingsServiceBean.Key.AllowCustomTermsOfUse, safeDefaultIfKeyNotFound)){ - return null; - } else { - return licenseService.getDefault(); - } + return null; } License license = licenseService.getByNameOrUri(licenseNameOrUri); if (license == null) throw new JsonParseException("Invalid license: " + licenseNameOrUri); @@ -714,12 +734,7 @@ private edu.harvard.iq.dataverse.license.License parseLicense(String licenseName private edu.harvard.iq.dataverse.license.License parseLicense(JsonObject licenseObj) throws JsonParseException { if (licenseObj == null){ - boolean safeDefaultIfKeyNotFound = true; - if (settingsService.isTrueForKey(SettingsServiceBean.Key.AllowCustomTermsOfUse, safeDefaultIfKeyNotFound)){ - return null; - } else { - return licenseService.getDefault(); - } + return null; } String licenseName = licenseObj.getString("name", null); diff --git a/src/test/java/edu/harvard/iq/dataverse/util/json/JsonParserTest.java b/src/test/java/edu/harvard/iq/dataverse/util/json/JsonParserTest.java index 262aa38d5d0..f696e5fcd4c 100644 --- a/src/test/java/edu/harvard/iq/dataverse/util/json/JsonParserTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/util/json/JsonParserTest.java @@ -19,6 +19,7 @@ import edu.harvard.iq.dataverse.dataset.DatasetType; import edu.harvard.iq.dataverse.dataset.DatasetTypeServiceBean; import edu.harvard.iq.dataverse.engine.command.DataverseRequest; +import edu.harvard.iq.dataverse.license.License; import edu.harvard.iq.dataverse.license.LicenseServiceBean; import edu.harvard.iq.dataverse.mocks.MockDatasetFieldSvc; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; @@ -758,7 +759,53 @@ public boolean assertFieldsEqual( DatasetField ex, DatasetField act ) { throw new IllegalArgumentException("Unknown dataset field type '" + ex.getDatasetFieldType() + "'"); } + @Test + public void testParseDatasetVersion_LicenseAndTerms() throws JsonParseException { + // Prepare mocks + License defaultLicense = new License(); + defaultLicense.setName("CC0 1.0"); + Mockito.when(licenseService.getDefault()).thenReturn(defaultLicense); + + String baseJson = "{\"metadataBlocks\":{\"citation\":{\"fields\":[]}}}"; + + // Case 1: Setting not set, AllowCustomTermsOfUse = false -> should pick default + settingsSvc.setAllowCustomTermsOfUse(false); + System.clearProperty("dataverse.api.assume-default-license-when-not-provided-via-api"); + DatasetVersion dsv1 = sut.parseDatasetVersion(JsonUtil.getJsonObject(baseJson)); + assertEquals(defaultLicense, dsv1.getTermsOfUseAndAccess().getLicense()); + + // Case 2: Setting not set, AllowCustomTermsOfUse = true -> should NOT pick default + settingsSvc.setAllowCustomTermsOfUse(true); + DatasetVersion dsv2 = sut.parseDatasetVersion(JsonUtil.getJsonObject(baseJson)); + assertNull(dsv2.getTermsOfUseAndAccess().getLicense()); + + // Case 3: assumeDefaultLicenseWhenNotProvidedViaApi = true, terms NOT provided -> should pick default + System.setProperty("dataverse.api.assume-default-license-when-not-provided-via-api", "true"); + DatasetVersion dsv3 = sut.parseDatasetVersion(JsonUtil.getJsonObject(baseJson)); + assertEquals(defaultLicense, dsv3.getTermsOfUseAndAccess().getLicense()); + + // Case 4: assumeDefaultLicenseWhenNotProvidedViaApi = true, terms PROVIDED -> should NOT pick default + String jsonWithTerms = "{\"metadataBlocks\":{\"citation\":{\"fields\":[]}}, \"termsOfUse\":\"Some terms\"}"; + DatasetVersion dsv4 = sut.parseDatasetVersion(JsonUtil.getJsonObject(jsonWithTerms)); + assertNull(dsv4.getTermsOfUseAndAccess().getLicense()); + assertEquals("Some terms", dsv4.getTermsOfUseAndAccess().getTermsOfUse()); + + // Case 5: assumeDefaultLicenseWhenNotProvidedViaApi = false -> should NOT pick default + System.setProperty("dataverse.api.assume-default-license-when-not-provided-via-api", "false"); + DatasetVersion dsv5 = sut.parseDatasetVersion(JsonUtil.getJsonObject(baseJson)); + assertNull(dsv5.getTermsOfUseAndAccess().getLicense()); + + // Cleanup + System.clearProperty("dataverse.api.assume-default-license-when-not-provided-via-api"); + } + private static class MockSettingsSvc extends SettingsServiceBean { + private boolean allowCustomTermsOfUse = false; + + public void setAllowCustomTermsOfUse(boolean allow) { + this.allowCustomTermsOfUse = allow; + } + @Override public String getValueForKey( Key key /*, String defaultValue */) { switch (key) { @@ -775,7 +822,7 @@ public String getValueForKey( Key key /*, String defaultValue */) { @Override public boolean isTrueForKey(Key key, boolean safeDefaultIfKeyNotFound) { if (key == Key.AllowCustomTermsOfUse) { - return false; + return this.allowCustomTermsOfUse; } return safeDefaultIfKeyNotFound; } From e8716e50e0b1483cbdfb3fb034af1d43310d404a Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Tue, 21 Jul 2026 17:16:18 -0400 Subject: [PATCH 2/5] release note --- .../11161-assume-default-license-release-note.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 doc/release-notes/11161-assume-default-license-release-note.md diff --git a/doc/release-notes/11161-assume-default-license-release-note.md b/doc/release-notes/11161-assume-default-license-release-note.md new file mode 100644 index 00000000000..fb14f3874c9 --- /dev/null +++ b/doc/release-notes/11161-assume-default-license-release-note.md @@ -0,0 +1,9 @@ +### New JVM Option: assume-default-license-when-not-provided-via-api + +A new JVM option `dataverse.api.assume-default-license-when-not-provided-via-api` has been added. This option controls the behavior when creating a dataset via API without providing a license or terms of use. + +- When set to `true`, the default license will be automatically assigned if no license and no terms are provided in the input JSON. +- When set to `false`, no license (and no terms) will be assigned if none are provided. +- When not set (the default), the system maintains backward compatibility: the default license is assigned if custom terms are not allowed; if custom terms are allowed, no license is assigned. + +This allows administrators more control over whether datasets created via API must have a license or can be created without one when terms are also missing. From 2b69f8052e6236c9ed92e1c5d5a2cdf6a8697e13 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Wed, 22 Jul 2026 12:42:09 -0400 Subject: [PATCH 3/5] only support full on/off --- ...161-assume-default-license-release-note.md | 9 ------ .../11161-do-not-assume-default-license.md | 8 +++++ doc/sphinx-guides/source/api/changelog.rst | 2 +- .../source/installation/config.rst | 23 ++++++-------- .../iq/dataverse/settings/FeatureFlags.java | 11 +++++++ .../iq/dataverse/settings/JvmSettings.java | 1 - .../iq/dataverse/util/json/JsonParser.java | 12 ++----- .../dataverse/util/json/JsonParserTest.java | 31 ++++++------------- 8 files changed, 41 insertions(+), 56 deletions(-) delete mode 100644 doc/release-notes/11161-assume-default-license-release-note.md create mode 100644 doc/release-notes/11161-do-not-assume-default-license.md diff --git a/doc/release-notes/11161-assume-default-license-release-note.md b/doc/release-notes/11161-assume-default-license-release-note.md deleted file mode 100644 index fb14f3874c9..00000000000 --- a/doc/release-notes/11161-assume-default-license-release-note.md +++ /dev/null @@ -1,9 +0,0 @@ -### New JVM Option: assume-default-license-when-not-provided-via-api - -A new JVM option `dataverse.api.assume-default-license-when-not-provided-via-api` has been added. This option controls the behavior when creating a dataset via API without providing a license or terms of use. - -- When set to `true`, the default license will be automatically assigned if no license and no terms are provided in the input JSON. -- When set to `false`, no license (and no terms) will be assigned if none are provided. -- When not set (the default), the system maintains backward compatibility: the default license is assigned if custom terms are not allowed; if custom terms are allowed, no license is assigned. - -This allows administrators more control over whether datasets created via API must have a license or can be created without one when terms are also missing. diff --git a/doc/release-notes/11161-do-not-assume-default-license.md b/doc/release-notes/11161-do-not-assume-default-license.md new file mode 100644 index 00000000000..5ac23b11584 --- /dev/null +++ b/doc/release-notes/11161-do-not-assume-default-license.md @@ -0,0 +1,8 @@ +### New Feature Flag: do-not-assume-default-license + +A new feature flag `do-not-assume-default-license` has been added. This flag controls the behavior when creating a dataset via API without providing a license or terms of use. + +- By default (flag disabled), the default license will be automatically assigned if no license and no terms are provided in the input JSON, regardless of whether custom terms are allowed. +- When enabled, no license (and no terms) will be assigned if none are provided. + +**Note:** Previously, if custom terms were allowed, the system would not assign a default license in this case. To retain that behavior, you must now enable this feature flag. diff --git a/doc/sphinx-guides/source/api/changelog.rst b/doc/sphinx-guides/source/api/changelog.rst index 874df2b2ed1..de6b8ed1bdd 100644 --- a/doc/sphinx-guides/source/api/changelog.rst +++ b/doc/sphinx-guides/source/api/changelog.rst @@ -15,7 +15,7 @@ v6.12 - **/api/admin/index/perms/{id}** -- Dataset creation API calls may now behave differently when neither a license nor terms are provided, depending on the new :ref:`dataverse.api.assume-default-license-when-not-provided-via-api` setting. +- Dataset creation API calls may now behave differently when neither a license nor terms are provided, depending on the new :ref:`dataverse.feature.do-not-assume-default-license` feature flag. v6.11 ----- diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index 6ff6a4d7f95..facadc7ffbf 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -3367,20 +3367,6 @@ Defaults to ``false``. Can also be set via any `supported MicroProfile Config API source`_, e.g. the environment variable ``DATAVERSE_API_ALLOW_INCOMPLETE_METADATA``. Will accept ``[tT][rR][uU][eE]|1|[oO][nN]`` as "true" expressions. -.. _dataverse.api.assume-default-license-when-not-provided-via-api: - -dataverse.api.assume-default-license-when-not-provided-via-api -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -When creating a dataset via API, if neither a license nor any terms of use are provided, this setting determines whether the default license is assigned. - -- If set to ``true``, the default license is assigned if no license and no terms are provided. -- If set to ``false``, no license is assigned (and no terms). -- If not set (default), the behavior depends on the :ref:`:AllowCustomTermsOfUse` setting: if custom terms are allowed, no license is assigned; if not allowed, the default license is assigned. - -Can also be set via any `supported MicroProfile Config API source`_, e.g. the environment variable -``DATAVERSE_API_ASSUME_DEFAULT_LICENSE_WHEN_NOT_PROVIDED_VIA_API``. Will accept ``[tT][rR][uU][eE]|1|[oO][nN]`` as "true" expressions. - .. _dataverse.api.blocked.endpoints: dataverse.api.blocked.endpoints @@ -4031,6 +4017,15 @@ dataverse.feature.api-bearer-auth-use-oauth-user-on-id-match Allows the use of an OAuth user account (GitHub, Google, or ORCID) when an identity match is found during API bearer authentication. This feature enables automatic association of an incoming IdP identity with an existing OAuth user account, bypassing the need for additional user registration steps. This feature only works when the feature flag ``api-bearer-auth`` is also enabled. **Caution: Enabling this flag could result in impersonation risks if (and only if) used with a misconfigured IdP.** +.. _dataverse.feature.do-not-assume-default-license: + +dataverse.feature.do-not-assume-default-license ++++++++++++++++++++++++++++++++++++++++++++++++ + +When creating a dataset via API, if neither a license nor any terms of use are provided, the system normally assigns the default license. If this feature flag is enabled, no license is assigned (and no terms) in this case. + +Defaults to ``false``. + .. _dataverse.feature.avoid-expensive-solr-join: dataverse.feature.avoid-expensive-solr-join 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 6ae5114015c..a170d24209d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/FeatureFlags.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/FeatureFlags.java @@ -98,6 +98,17 @@ public enum FeatureFlags { */ API_BEARER_AUTH_USE_OAUTH_USER_ON_ID_MATCH("api-bearer-auth-use-oauth-user-on-id-match"), + /** + * When creating a dataset via API, if neither a license nor any terms of use are provided, + * the system normally assigns the default license. + * + *

If this feature flag is enabled, no license is assigned (and no terms) in this case.

+ * + * @apiNote Raise flag by setting "dataverse.feature.do-not-assume-default-license" + * @since Dataverse 6.5 + */ + DO_NOT_ASSUME_DEFAULT_LICENSE("do-not-assume-default-license"), + /** * For published (public) objects, don't use a join when searching Solr. * Experimental! Requires a reindex with the following feature flag enabled, 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 cbcc42e1041..4c614ee2233 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/JvmSettings.java @@ -96,7 +96,6 @@ public enum JvmSettings { // API: MDC Citation updates SCOPE_API_MDC(SCOPE_API, "mdc"), API_MDC_UPDATE_MIN_DELAY_MS(SCOPE_API_MDC, "min-delay-ms"), - ASSUME_DEFAULT_LICENSE_WHEN_NOT_PROVIDED_VIA_API(SCOPE_API, "assume-default-license-when-not-provided-via-api"), // SIGNPOSTING SETTINGS SCOPE_SIGNPOSTING(PREFIX, "signposting"), diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java index 1fde24f3852..cd29b0b00c5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java @@ -17,7 +17,6 @@ import edu.harvard.iq.dataverse.license.License; import edu.harvard.iq.dataverse.license.LicenseServiceBean; import edu.harvard.iq.dataverse.settings.FeatureFlags; -import edu.harvard.iq.dataverse.settings.JvmSettings; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import edu.harvard.iq.dataverse.util.BundleUtil; import edu.harvard.iq.dataverse.util.StringUtil; @@ -531,15 +530,8 @@ public DatasetVersion parseDatasetVersion(JsonObject obj, DatasetVersion dsv) th || terms.getConditions() != null || terms.getDisclaimer() != null; - Optional assumeDefault = JvmSettings.ASSUME_DEFAULT_LICENSE_WHEN_NOT_PROVIDED_VIA_API.lookupOptional(Boolean.class); - - if (assumeDefault.isPresent()) { - if (assumeDefault.get() && !termsProvided) { - license = licenseService.getDefault(); - } - } else { - // Default behavior (when setting not set) - if (!settingsService.isTrueForKey(SettingsServiceBean.Key.AllowCustomTermsOfUse, true)) { + if (!FeatureFlags.DO_NOT_ASSUME_DEFAULT_LICENSE.enabled()) { + if (!termsProvided) { license = licenseService.getDefault(); } } diff --git a/src/test/java/edu/harvard/iq/dataverse/util/json/JsonParserTest.java b/src/test/java/edu/harvard/iq/dataverse/util/json/JsonParserTest.java index f696e5fcd4c..f5d19400462 100644 --- a/src/test/java/edu/harvard/iq/dataverse/util/json/JsonParserTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/util/json/JsonParserTest.java @@ -768,35 +768,24 @@ public void testParseDatasetVersion_LicenseAndTerms() throws JsonParseException String baseJson = "{\"metadataBlocks\":{\"citation\":{\"fields\":[]}}}"; - // Case 1: Setting not set, AllowCustomTermsOfUse = false -> should pick default - settingsSvc.setAllowCustomTermsOfUse(false); - System.clearProperty("dataverse.api.assume-default-license-when-not-provided-via-api"); + // Case 1: Flag false (default), terms NOT provided -> should pick default + System.setProperty("dataverse.feature.do-not-assume-default-license", "false"); DatasetVersion dsv1 = sut.parseDatasetVersion(JsonUtil.getJsonObject(baseJson)); assertEquals(defaultLicense, dsv1.getTermsOfUseAndAccess().getLicense()); - // Case 2: Setting not set, AllowCustomTermsOfUse = true -> should NOT pick default - settingsSvc.setAllowCustomTermsOfUse(true); - DatasetVersion dsv2 = sut.parseDatasetVersion(JsonUtil.getJsonObject(baseJson)); + // Case 2: Flag false (default), terms PROVIDED -> should NOT pick default + String jsonWithTerms = "{\"metadataBlocks\":{\"citation\":{\"fields\":[]}}, \"termsOfUse\":\"Some terms\"}"; + DatasetVersion dsv2 = sut.parseDatasetVersion(JsonUtil.getJsonObject(jsonWithTerms)); assertNull(dsv2.getTermsOfUseAndAccess().getLicense()); + assertEquals("Some terms", dsv2.getTermsOfUseAndAccess().getTermsOfUse()); - // Case 3: assumeDefaultLicenseWhenNotProvidedViaApi = true, terms NOT provided -> should pick default - System.setProperty("dataverse.api.assume-default-license-when-not-provided-via-api", "true"); + // Case 3: Flag true, terms NOT provided -> should NOT pick default + System.setProperty("dataverse.feature.do-not-assume-default-license", "true"); DatasetVersion dsv3 = sut.parseDatasetVersion(JsonUtil.getJsonObject(baseJson)); - assertEquals(defaultLicense, dsv3.getTermsOfUseAndAccess().getLicense()); - - // Case 4: assumeDefaultLicenseWhenNotProvidedViaApi = true, terms PROVIDED -> should NOT pick default - String jsonWithTerms = "{\"metadataBlocks\":{\"citation\":{\"fields\":[]}}, \"termsOfUse\":\"Some terms\"}"; - DatasetVersion dsv4 = sut.parseDatasetVersion(JsonUtil.getJsonObject(jsonWithTerms)); - assertNull(dsv4.getTermsOfUseAndAccess().getLicense()); - assertEquals("Some terms", dsv4.getTermsOfUseAndAccess().getTermsOfUse()); - - // Case 5: assumeDefaultLicenseWhenNotProvidedViaApi = false -> should NOT pick default - System.setProperty("dataverse.api.assume-default-license-when-not-provided-via-api", "false"); - DatasetVersion dsv5 = sut.parseDatasetVersion(JsonUtil.getJsonObject(baseJson)); - assertNull(dsv5.getTermsOfUseAndAccess().getLicense()); + assertNull(dsv3.getTermsOfUseAndAccess().getLicense()); // Cleanup - System.clearProperty("dataverse.api.assume-default-license-when-not-provided-via-api"); + System.clearProperty("dataverse.feature.do-not-assume-default-license"); } private static class MockSettingsSvc extends SettingsServiceBean { From a018fdbc3d8379759d3fb66499bf69d45a9bd266 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Wed, 22 Jul 2026 15:00:46 -0400 Subject: [PATCH 4/5] fix tests, fully enforce no license and terms in TofU&A --- .../iq/dataverse/TermsOfUseAndAccess.java | 24 +++++++++ .../iq/dataverse/util/json/JsonParser.java | 37 ++++++------- .../iq/dataverse/TermsOfUseAndAccessTest.java | 54 +++++++++++++++++++ 3 files changed, 97 insertions(+), 18 deletions(-) create mode 100644 src/test/java/edu/harvard/iq/dataverse/TermsOfUseAndAccessTest.java diff --git a/src/main/java/edu/harvard/iq/dataverse/TermsOfUseAndAccess.java b/src/main/java/edu/harvard/iq/dataverse/TermsOfUseAndAccess.java index 9e48c6c0165..c91533e8677 100644 --- a/src/main/java/edu/harvard/iq/dataverse/TermsOfUseAndAccess.java +++ b/src/main/java/edu/harvard/iq/dataverse/TermsOfUseAndAccess.java @@ -161,6 +161,9 @@ public String getTermsOfUse() { public void setTermsOfUse(String termsOfUse) { this.termsOfUse = termsOfUse; + if (termsOfUse != null) { + this.license = null; + } } public String getTermsOfAccess() { @@ -177,6 +180,9 @@ public String getConfidentialityDeclaration() { public void setConfidentialityDeclaration(String confidentialityDeclaration) { this.confidentialityDeclaration = confidentialityDeclaration; + if (confidentialityDeclaration != null) { + this.license = null; + } } public String getSpecialPermissions() { @@ -185,6 +191,9 @@ public String getSpecialPermissions() { public void setSpecialPermissions(String specialPermissions) { this.specialPermissions = specialPermissions; + if (specialPermissions != null) { + this.license = null; + } } public String getRestrictions() { @@ -193,6 +202,9 @@ public String getRestrictions() { public void setRestrictions(String restrictions) { this.restrictions = restrictions; + if (restrictions != null) { + this.license = null; + } } public String getCitationRequirements() { @@ -201,6 +213,9 @@ public String getCitationRequirements() { public void setCitationRequirements(String citationRequirements) { this.citationRequirements = citationRequirements; + if (citationRequirements != null) { + this.license = null; + } } public String getDepositorRequirements() { @@ -209,6 +224,9 @@ public String getDepositorRequirements() { public void setDepositorRequirements(String depositorRequirements) { this.depositorRequirements = depositorRequirements; + if (depositorRequirements != null) { + this.license = null; + } } public String getConditions() { @@ -217,6 +235,9 @@ public String getConditions() { public void setConditions(String conditions) { this.conditions = conditions; + if (conditions != null) { + this.license = null; + } } public String getDisclaimer() { @@ -225,6 +246,9 @@ public String getDisclaimer() { public void setDisclaimer(String disclaimer) { this.disclaimer = disclaimer; + if (disclaimer != null) { + this.license = null; + } } public String getDataAccessPlace() { diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java index cd29b0b00c5..bcd7839d908 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java @@ -491,24 +491,6 @@ public DatasetVersion parseDatasetVersion(JsonObject obj, DatasetVersion dsv) th } } - //test to see if license exists in dataset type - //if not set it to null - - //only test if Dataset has a type and if it has custom available licenses - if (dsv.getDataset() != null) { - DatasetType dst = dsv.getDataset().getDatasetType(); - if (dst != null && dst.getLicenses() != null && !dst.getLicenses().isEmpty() && license != null) { - boolean invalidLicense = true; - for (License testLicense : dst.getLicenses()) { - if (testLicense.equals(license)) { - invalidLicense = false; - } - } - if (invalidLicense) { - license = null; - } - } - } - terms.setTermsOfUse(obj.getString("termsOfUse", null)); terms.setConfidentialityDeclaration(obj.getString("confidentialityDeclaration", null)); terms.setSpecialPermissions(obj.getString("specialPermissions", null)); @@ -547,6 +529,25 @@ public DatasetVersion parseDatasetVersion(JsonObject obj, DatasetVersion dsv) th terms.setFileAccessRequest(obj.getBoolean("fileAccessRequest", false)); dsv.setTermsOfUseAndAccess(terms); terms.setDatasetVersion(dsv); + + //test to see if license exists in dataset type + //if not set it to null - + //only test if Dataset has a type and if it has custom available licenses + if (dsv.getDataset() != null) { + DatasetType dst = dsv.getDataset().getDatasetType(); + if (dst != null && dst.getLicenses() != null && !dst.getLicenses().isEmpty() && license != null) { + boolean invalidLicense = true; + for (License testLicense : dst.getLicenses()) { + if (testLicense.equals(license)) { + invalidLicense = false; + } + } + if (invalidLicense) { + license = null; + } + } + } + JsonObject metadataBlocks = obj.getJsonObject("metadataBlocks"); if (metadataBlocks == null){ throw new JsonParseException(BundleUtil.getStringFromBundle("jsonparser.error.metadatablocks.not.found")); diff --git a/src/test/java/edu/harvard/iq/dataverse/TermsOfUseAndAccessTest.java b/src/test/java/edu/harvard/iq/dataverse/TermsOfUseAndAccessTest.java new file mode 100644 index 00000000000..cb542db4b3c --- /dev/null +++ b/src/test/java/edu/harvard/iq/dataverse/TermsOfUseAndAccessTest.java @@ -0,0 +1,54 @@ +package edu.harvard.iq.dataverse; + +import org.junit.jupiter.api.Test; +import edu.harvard.iq.dataverse.license.License; +import static org.junit.jupiter.api.Assertions.*; + +public class TermsOfUseAndAccessTest { + + @Test + public void testLicenseAndTermsMutualExclusivity() { + License license = new License(); + license.setName("CC0"); + + TermsOfUseAndAccess terms = new TermsOfUseAndAccess(); + + // 1. Setting license should clear terms + terms.setTermsOfUse("Some terms"); + assertEquals("Some terms", terms.getTermsOfUse()); + assertNull(terms.getLicense()); + + terms.setLicense(license); + assertNull(terms.getTermsOfUse()); + assertEquals(license, terms.getLicense()); + + // 2. Setting terms should clear license + terms.setTermsOfUse("New terms"); + assertNull(terms.getLicense()); + assertEquals("New terms", terms.getTermsOfUse()); + + // 3. Test other fields clear license too + terms.setLicense(license); + terms.setConfidentialityDeclaration("Confidential"); + assertNull(terms.getLicense()); + assertEquals("Confidential", terms.getConfidentialityDeclaration()); + } + + @Test + public void testCopyTermsOfUseAndAccess() { + License license = new License(); + license.setName("CC0"); + + TermsOfUseAndAccess terms = new TermsOfUseAndAccess(); + terms.setLicense(license); + + TermsOfUseAndAccess copy = terms.copyTermsOfUseAndAccess(); + assertEquals(license, copy.getLicense()); + assertNull(copy.getTermsOfUse()); + + terms.setTermsOfUse("Some terms"); + TermsOfUseAndAccess copy2 = terms.copyTermsOfUseAndAccess(); + assertNull(copy2.getLicense()); + assertEquals("Some terms", copy2.getTermsOfUse()); + } +} From f9b46f3b5f508b144bbb39c851edf9d9a89400c6 Mon Sep 17 00:00:00 2001 From: Jim Myers Date: Wed, 22 Jul 2026 16:53:22 -0400 Subject: [PATCH 5/5] move DatasetType logic before setLicense() --- .../iq/dataverse/util/json/JsonParser.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java index bcd7839d908..e6e0c71c396 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java @@ -518,17 +518,6 @@ public DatasetVersion parseDatasetVersion(JsonObject obj, DatasetVersion dsv) th } } } - terms.setLicense(license); - terms.setTermsOfAccess(obj.getString("termsOfAccess", null)); - terms.setDataAccessPlace(obj.getString("dataAccessPlace", null)); - terms.setOriginalArchive(obj.getString("originalArchive", null)); - terms.setAvailabilityStatus(obj.getString("availabilityStatus", null)); - terms.setContactForAccess(obj.getString("contactForAccess", null)); - terms.setSizeOfCollection(obj.getString("sizeOfCollection", null)); - terms.setStudyCompletion(obj.getString("studyCompletion", null)); - terms.setFileAccessRequest(obj.getBoolean("fileAccessRequest", false)); - dsv.setTermsOfUseAndAccess(terms); - terms.setDatasetVersion(dsv); //test to see if license exists in dataset type //if not set it to null - @@ -548,6 +537,18 @@ public DatasetVersion parseDatasetVersion(JsonObject obj, DatasetVersion dsv) th } } + terms.setLicense(license); + terms.setTermsOfAccess(obj.getString("termsOfAccess", null)); + terms.setDataAccessPlace(obj.getString("dataAccessPlace", null)); + terms.setOriginalArchive(obj.getString("originalArchive", null)); + terms.setAvailabilityStatus(obj.getString("availabilityStatus", null)); + terms.setContactForAccess(obj.getString("contactForAccess", null)); + terms.setSizeOfCollection(obj.getString("sizeOfCollection", null)); + terms.setStudyCompletion(obj.getString("studyCompletion", null)); + terms.setFileAccessRequest(obj.getBoolean("fileAccessRequest", false)); + dsv.setTermsOfUseAndAccess(terms); + terms.setDatasetVersion(dsv); + JsonObject metadataBlocks = obj.getJsonObject("metadataBlocks"); if (metadataBlocks == null){ throw new JsonParseException(BundleUtil.getStringFromBundle("jsonparser.error.metadatablocks.not.found"));