diff --git a/dataverse-persistence/src/main/resources/Bundle_en.properties b/dataverse-persistence/src/main/resources/Bundle_en.properties index c3856607f8b..cb3da7470ee 100755 --- a/dataverse-persistence/src/main/resources/Bundle_en.properties +++ b/dataverse-persistence/src/main/resources/Bundle_en.properties @@ -3078,7 +3078,8 @@ uningest.table.unf=UNF of ingested file uningest.table.actions=Action uningest.table.uningest.button=Uningest uningest.dialog.title=Confirm uningest -uningest.dialog.text=Do you really want to uningest the file named "{0}"? +uningest.dialog.text=Do you really want to uningest the selected files? +uningest.error=Could not uningest the following files add.dataset.button=Add dataset add.dataset.dialog.header=Select dataverse diff --git a/dataverse-persistence/src/main/resources/Bundle_pl.properties b/dataverse-persistence/src/main/resources/Bundle_pl.properties index 83c9c3283fa..e5ab93944fa 100644 --- a/dataverse-persistence/src/main/resources/Bundle_pl.properties +++ b/dataverse-persistence/src/main/resources/Bundle_pl.properties @@ -2334,8 +2334,8 @@ mydataFragment.errorMessage.NoUserSelected=Przykro mi! U\u017Cytkownik nie zosta mydataFragment.errorMessage.UnknownType.Prefix=Przykro mi! Typ ' mydataFragment.errorMessage.UnknownType.Suffix=' jest nieznany. -file.provenance=Pochodzenie -file.editProvenanceDialog=Pochodzenie +file.provenance=Proweniencja +file.editProvenanceDialog=Proweniencja file.editProvenanceDialog.tip=Proweniencja to zapis \u017Ar\u00F3d\u0142a Twojego pliku danych i wszelkich transformacji, jakie przeszed\u0142. Prze\u015Blij plik JSON z narz\u0119dzia do przechwytywania proweniencji w celu wygenerowania grafu proweniencji swoich danych. Wi\u0119cej informacji mo\u017Cna znale\u017A\u0107 w Podr\u0119czniku u\u017Cytkownika. file.editProvenanceDialog.upload.invalidFile=Nie mo\u017Cna przes\u0142a\u0107 pliku. Spr\u00F3buj ponownie u\u017Cywaj\u0105c pliku w formacie JSON. file.editProvenanceDialog.uploadSuccess=Przesy\u0142anie uko\u0144czone @@ -2344,7 +2344,7 @@ file.editProvenanceDialog.noEntitiesError=Przes\u0142any plik z rejestrem prowen file.editProvenanceDialog.invalidSchemaError=Przes\u0142any plik z rejestrem proweniencji nie jest zgodny ze standardem PROV W3C. file.editProvenanceDialog.bundleFile=Plik z rejestrem proweniencji file.editProvenanceDialog.bundleFile.instructions=Plik musi by\u0107 w formacie JSON i spe\u0142nia\u0107 wymogi standardu W3C. -file.editProvenanceDialog.bundleFile.alreadyPublished=Ten plik z rejestrem pochodzenia zosta\u0142 opublikowany i nie mo\u017Cna go zast\u0105pi\u0107 ani usun\u0105\u0107. +file.editProvenanceDialog.bundleFile.alreadyPublished=Ten plik z rejestrem proweniencji zosta\u0142 opublikowany i nie mo\u017Cna go zast\u0105pi\u0107 ani usun\u0105\u0107. file.editProvenanceDialog.bundleEntity=Jednostka (entity) pliku danych file.editProvenanceDialog.bundleEntity.placeholder=Po\u0142\u0105cz jednostk\u0119 (entity)\u2026 file.editProvenanceDialog.bundleEntity.requiredValidation=Warto\u015B\u0107 jest wymagana @@ -2354,10 +2354,10 @@ file.editProvenanceDialog.bundleEntity.typeHeader=Typ file.editProvenanceDialog.bundleEntity.entityHeader=Jednostka (entity) file.editProvenanceDialog.selectToAddBtn=Wybierz plik file.editProvenanceDialog.description.tip=Mo\u017Cesz r\u00F3wnie\u017C doda\u0107 dokumentacj\u0119 historii Twojego pliku danych, zawieraj\u0105c\u0105 informacje o tym, jak powsta\u0142, jak si\u0119 zmienia\u0142 i kto nad nim pracowa\u0142. -file.editProvenanceDialog.description=Opis pochodzenia -file.editProvenanceDialog.description.placeholder=Dodaj opis pochodzenia -file.confirmProvenanceDialog=Pochodzenie -file.confirmProvenanceDialog.tip1=Po opublikowaniu tego pliku danych, Twojego pliku z rejestrem pochodzenia nie b\u0119dzie mo\u017Cna edytowa\u0107, ani zast\u0105pi\u0107. +file.editProvenanceDialog.description=Opis proweniencji +file.editProvenanceDialog.description.placeholder=Dodaj opis proweniencji +file.confirmProvenanceDialog=Proweniencja +file.confirmProvenanceDialog.tip1=Po opublikowaniu tego pliku danych, Twojego pliku z rejestrem proweniencji nie b\u0119dzie mo\u017Cna edytowa\u0107, ani zast\u0105pi\u0107. file.confirmProvenanceDialog.tip2=Wybierz "Anuluj" by powr\u00F3ci\u0107 do poprzedniej strony, gdzie mo\u017Cesz obejrze\u0107 podgl\u0105d swojego pliku z rejestrem proweniencji, by potwierdzi\u0107 jego poprawno\u015B\u0107. file.metadataTab.provenance.header=Proweniencja pliku file.metadataTab.provenance.body=Informacja o proweniencji pliku, kt\u00F3ra pojawi si\u0119 w p\u00F3\u017Aniejszej wersji\u2026 @@ -2370,8 +2370,8 @@ file.provConfirm.empty=Nie dokonano \u017Cadnych zmian. file.provAlert.published.json=Zmiany w Twoim pliku z rejestrem proweniencji zosta\u0142y zapisane w zbiorze danych. file.provAlert.unpublished.json=Zmiany w Twoim pliku z rejestrem proweniencji zostan\u0105 zapisane w tej wersji zbioru danych po naci\u015Bni\u0119ciu przycisku "Zapisz zmiany". file.provAlert.freeform=Zmiany w opisie proweniencji zostan\u0105 zapisane w tej wersji zbioru danych po naci\u015Bni\u0119ciu przycisku "Zapisz zmiany". -file.provAlert.filePage.published.json=Zmiany w Twoim pliku z rejestrem pochodzenia zosta\u0142y zapisane w zbiorze danych. -file.provAlert.filePage.unpublished.json=Zmiany w Twoim pliku z rejestrem pochodzenia zosta\u0142y zapisane w tej wersji zbioru danych. +file.provAlert.filePage.published.json=Zmiany w Twoim pliku z rejestrem proweniencji zosta\u0142y zapisane w zbiorze danych. +file.provAlert.filePage.unpublished.json=Zmiany w Twoim pliku z rejestrem proweniencji zosta\u0142y zapisane w tej wersji zbioru danych. file.provAlert.filePage.freeform=Zmiany w opisie proweniencji zosta\u0142y zapisane w tej wersji zbioru danych. api.prov.provJsonSaved=Zapisano rejestr proweniencji PROV-JSON dla pliku danych: @@ -3029,7 +3029,8 @@ uningest.table.unf=UNF zanalizowanego pliku uningest.table.actions=Akcje uningest.table.uningest.button=Cofnij analiz\u0119 uningest.dialog.title=Potwierd\u017A cofni\u0119cie analizy -uningest.dialog.text=Czy na pewno chcesz cofn\u0105\u0107 analiz\u0119 pliku o nazwie "{0}"? +uningest.dialog.text=Czy na pewno chcesz cofn\u0105\u0107 analiz\u0119 dla zaznaczonych plik\u00F3w? +uningest.error=Nie uda\u0142o si\u0119 cofn\u0105\u0107 analiz\u0119 dla nast\u0119puj\u0105cych plik\u00F3w add.dataset.button=Dodaj zbi\u00F3r danych add.dataset.dialog.header=Wybierz kolekcj\u0119 diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/DataverseSessionConfigListener.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/DataverseSessionConfigListener.java new file mode 100644 index 00000000000..ae296c8cd72 --- /dev/null +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/DataverseSessionConfigListener.java @@ -0,0 +1,47 @@ +package edu.harvard.iq.dataverse; + +import edu.harvard.iq.dataverse.util.SystemConfig; + +import javax.inject.Inject; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; +import javax.servlet.SessionCookieConfig; +import java.util.logging.Logger; + +@WebListener +public class DataverseSessionConfigListener implements ServletContextListener { + private final SystemConfig systemConfig; + private static final Logger logger = Logger.getLogger(DataverseSessionConfigListener.class.getCanonicalName()); + + @Inject + public DataverseSessionConfigListener(SystemConfig systemConfig) { + this.systemConfig = systemConfig; + } + + @Override + public void contextInitialized(ServletContextEvent sce) { + logger.info("Initializing session cookie configuration"); + SessionCookieConfig sessionCookieConfig = sce.getServletContext().getSessionCookieConfig(); + String cookieName = systemConfig.getCookieName(); + if (cookieName != null && !cookieName.isEmpty()) { + logger.info("Setting session cookie name to " + cookieName); + sessionCookieConfig.setName(cookieName); + } + String cookieDomain = systemConfig.getCookieDomain(); + if (cookieDomain != null && !cookieName.isEmpty()) { + logger.info("Setting session cookie domain to " + cookieDomain); + sessionCookieConfig.setDomain(cookieDomain); + } + Boolean cookieSecure = systemConfig.getCookieSecure(); + if (cookieSecure != null && !cookieName.isEmpty()) { + logger.info("Setting session cookie secure to " + cookieSecure); + sessionCookieConfig.setSecure(cookieSecure); + } + } + + @Override + public void contextDestroyed(ServletContextEvent servletContextEvent) { + // nothing to do here + } +} diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/LoginPage.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/LoginPage.java index 99baaf0a84a..b2b4d51ee7f 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/LoginPage.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/LoginPage.java @@ -22,6 +22,7 @@ import javax.faces.validator.ValidatorException; import javax.inject.Inject; import javax.inject.Named; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; @@ -31,6 +32,7 @@ import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; /** * @author xyang @@ -184,7 +186,12 @@ public String login() { } logger.log(Level.FINE, "Sending user to = {0}", redirectPage); - return redirectPage + (!redirectPage.contains("?") ? "?" : "&") + "faces-redirect=true"; + + if(validateIsRedirectUrlAnExternalResource(redirectPage)) { + return redirectToExternalResource(); + } else { + return redirectPage + (!redirectPage.contains("?") ? "?" : "&") + "faces-redirect=true"; + } } catch (AuthenticationFailedException ex) { numFailedLoginAttempts++; op1 = random.nextInt(10); @@ -212,6 +219,15 @@ public String login() { } } + boolean validateIsRedirectUrlAnExternalResource(String urlToValidate) { + boolean result = Pattern.compile("^(https?)://[^\\s/$.?#].[^\\s]*$", + Pattern.CASE_INSENSITIVE).matcher(urlToValidate).matches(); + if(!result) { + logger.severe("Invalid redirect URL: " + urlToValidate + ". Redirect URL must start with http:// or https://"); + } + return result; + } + public void resetFilledCredentials(AjaxBehaviorEvent event) { if (selectedCredentialsProvider() == null) { return; @@ -278,6 +294,28 @@ private String redirectToRoot() { return "dataverse.xhtml?alias=" + dataverseDao.findRootDataverse().getAlias(); } + private String redirectToExternalResource() { + try { + logger.info("Trying to redirect to external page: " + redirectPage); + if(systemConfig.getAllowedExternalRedirectionUrl() == null || systemConfig.getAllowedExternalRedirectionUrl().isEmpty()) { + logger.severe("External redirection not allowed."); + } else if(redirectPage.startsWith(systemConfig.getAllowedExternalRedirectionUrl())) { + FacesContext.getCurrentInstance().getExternalContext().redirect(redirectPage); + } else { + logger.severe("Chosen redirect page " + redirectPage + " is not allowed. " + + "Allowed pages: " + systemConfig.getAllowedExternalRedirectionUrl()); + } + } catch (IOException e) { + logger.severe("Unable to redirect to external page "+ e.getMessage()); + } + // Internal Redirection: Uses navigation handling in JSF, where returning + // a string tells JSF which page to navigate to next. + // External Redirection: Directly interacts with the HTTP response to send a redirect. + // No string return is necessary because the redirection is handled immediately + // by the ExternalContext.redirect() method. + return ""; + } + // -------------------- SETTERS -------------------- public void setSelectedSamlIdpId(Long selectedSamlIdpId) { diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/UningestPage.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/UningestPage.java index 5ca876b95ab..8753ba4998c 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/UningestPage.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/UningestPage.java @@ -1,5 +1,6 @@ package edu.harvard.iq.dataverse; +import edu.harvard.iq.dataverse.common.BundleUtil; import edu.harvard.iq.dataverse.ingest.UningestInfoService; import edu.harvard.iq.dataverse.ingest.UningestService; import edu.harvard.iq.dataverse.persistence.datafile.DataFile; @@ -11,7 +12,11 @@ import edu.harvard.iq.dataverse.util.SystemConfig; import org.apache.commons.lang.StringUtils; import org.omnifaces.cdi.ViewScoped; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; import javax.inject.Inject; import javax.inject.Named; import java.io.Serializable; @@ -23,6 +28,7 @@ @Named("UningestPage") public class UningestPage implements Serializable { + private static final Logger log = LoggerFactory.getLogger(UningestPage.class); private UningestService uningestService; private DatasetRepository datasetRepository; private DataverseSession dataverseSession; @@ -33,7 +39,7 @@ public class UningestPage implements Serializable { private UningestInfoService uningestInfoService; private List uningestableFiles = new ArrayList<>(); - private UningestableItem toUningest; + private List selectedFiles = new ArrayList<>(); private Long datasetId; private Dataset dataset; @@ -51,9 +57,9 @@ public Dataset getDataset() { public List getUningestableFiles() { return uningestableFiles; } - - public UningestableItem getToUningest() { - return toUningest; + + public List getSelectedFiles() { + return selectedFiles; } // -------------------- CONSTRUCTORS -------------------- @@ -96,17 +102,33 @@ public String init() { return permissionsWrapper.notFound(); } uningestableFiles.addAll(prepareItemList()); + selectedFiles.clear(); return StringUtils.EMPTY; } public void uningest() { - if (toUningest == null || !dataverseSession.getUser().isAuthenticated()) { + if (selectedFiles.isEmpty() || !dataverseSession.getUser().isAuthenticated()) { return; } + AuthenticatedUser user = (AuthenticatedUser) dataverseSession.getUser(); - uningestService.uningest(toUningest.getDataFile(), user); + List uningestFailedFileNames = new ArrayList<>(); + selectedFiles.forEach(toUningest -> { + try { + uningestService.uningest(toUningest.getDataFile(), user); + } catch (Exception e) { + log.error("Could not uningest data file: {}", toUningest.getDataFile().getId(), e); + uningestFailedFileNames.add(toUningest.getFileName()); + } + }); uningestableFiles = prepareItemList(); - toUningest = null; + selectedFiles.clear(); + + if (!uningestFailedFileNames.isEmpty()) { + FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, + BundleUtil.getStringFromBundle("uningest.error"), + uningestFailedFileNames.stream().collect(Collectors.joining(", ", "[", "]. ")))); + } } public String cancel() { @@ -127,8 +149,8 @@ public void setDatasetId(Long datasetId) { this.datasetId = datasetId; } - public void setToUningest(UningestableItem toUningest) { - this.toUningest = toUningest; + public void setSelectedFiles(List selectedFiles) { + this.selectedFiles = selectedFiles; } // -------------------- INNER CLASSES -------------------- diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java index 39299dc2bb6..f187753e53e 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java @@ -322,6 +322,10 @@ public enum Key { * Location and name of Footer customization file */ FooterCustomizationFile, + /** + * Additional links to be shown in the footer. + */ + FooterAdditionalUrl, /** * Location and name of CSS customization file (it will be used as an inline style) */ @@ -737,7 +741,13 @@ public enum Key { * Additional (localized) text to show at the top * of the "Add dataset" modal window. */ - SelectDataverseInfo + SelectDataverseInfo, + + CookieDomain, + CookieName, + CookieSecure, + + AllowedExternalRedirectionUrlAfterLogin ; diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/settings/SettingsWrapper.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/settings/SettingsWrapper.java index e80bf111d8f..a5c28f25dc2 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/settings/SettingsWrapper.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/settings/SettingsWrapper.java @@ -32,7 +32,8 @@ public class SettingsWrapper implements java.io.Serializable { SystemConfig systemConfig; private final LazyLoaded> configuredLocales = new LazyLoaded<>(this::languagesLoader); - private final LazyLoaded> configuredAboutUrls = new LazyLoaded<>(this::aboutUrlsLoader); + private final LazyLoaded> configuredAboutUrls = new LazyLoaded<>(() -> urlsLoader(SettingsServiceBean.Key.NavbarAboutUrl)); + private final LazyLoaded> configuredFooterUrls = new LazyLoaded<>(() -> urlsLoader(SettingsServiceBean.Key.FooterAdditionalUrl)); // -------------------- GETTERS -------------------- @@ -122,6 +123,10 @@ public Map getConfiguredAboutUrls() { return configuredAboutUrls.get(); } + public Map getConfiguredFooterUrls() { + return configuredFooterUrls.get(); + } + public boolean isDataCiteInstallation() { String protocol = getEnumSettingValue(SettingsServiceBean.Key.DoiProvider); return "DataCite".equals(protocol); @@ -134,9 +139,9 @@ private Map languagesLoader() { .collect(toMap(getKey("locale"), getKey("title"), throwingMerger(), LinkedHashMap::new)); } - private Map aboutUrlsLoader() { + private Map urlsLoader(SettingsServiceBean.Key key) { String lang = FacesContext.getCurrentInstance().getViewRoot().getLocale().getLanguage(); - return settingService.getValueForKeyAsListOfMaps(SettingsServiceBean.Key.NavbarAboutUrl).stream() + return settingService.getValueForKeyAsListOfMaps(key).stream() .collect(toMap(getKey("url"), getKey("title." + lang), throwingMerger(), LinkedHashMap::new)); } diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java index c53d8eb1e4f..77f948a4c77 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java @@ -278,6 +278,23 @@ public String getSelectDataverseInfo(Locale locale) { return getLocalizedProperty(SettingsServiceBean.Key.SelectDataverseInfo, locale); } + + public String getAllowedExternalRedirectionUrl() { + return settingsService.getValueForKey(SettingsServiceBean.Key.AllowedExternalRedirectionUrlAfterLogin); + } + + public String getCookieName() { + return settingsService.getValueForKey(Key.CookieName); + } + + public String getCookieDomain() { + return settingsService.getValueForKey(Key.CookieDomain); + } + + public Boolean getCookieSecure() { + return Boolean.parseBoolean(settingsService.getValueForKey(Key.CookieSecure)); + } + public long getTabularIngestSizeLimit() { // This method will return the blanket ingestable size limit, if // set on the system. I.e., the universal limit that applies to all diff --git a/dataverse-webapp/src/main/resources/config/dataverse.default.properties b/dataverse-webapp/src/main/resources/config/dataverse.default.properties index 545381c8bd8..c37989f9012 100644 --- a/dataverse-webapp/src/main/resources/config/dataverse.default.properties +++ b/dataverse-webapp/src/main/resources/config/dataverse.default.properties @@ -126,6 +126,8 @@ ShowAccessibilityStatementFooterLink=false BlockedApiKey= BuiltinUsers.KEY= FooterCopyright= +#FooterAdditionalUrl=[{"url":"http://example.com", "title.pl":"Ta daa", "title.en":"Something"}] +FooterAdditionalUrl=[] ApplicationPrivacyPolicyUrl= #NavbarAboutUrl=[{"url":"http://example.com", "title.pl":"O repozytorium", "title.en":"About repository"}] NavbarAboutUrl=[] @@ -185,3 +187,22 @@ LoginInfo= # Select dataverse info with locale suffixes (eg. SelectDataverseInfo.pl; default english without suffix) # holds the text which will be shown on top of the "Add dataset" dialog SelectDataverseInfo= + +# Externalised session cookie configuration. +# see: dataverse-webapp/src/main/webapp/WEB-INF/web.xml +# see: dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/DataverseSessionConfigListener.java +# f.e.: CookieDomain=.example.com +# f.e.: CookieName=CUSTOMSESSIONID +# f.e.: CookieSecure=true +CookieDomain= +CookieName= +CookieSecure= + +# External (not within this Dataverse instance) redirect target URL after +# a successful login, or URL fragment the redirect target must start with. +# Must start with http or https. Passed as a `redirectPage` request +# parameter: `/loginpage.xhtml?redirectPage=https://example.com` +# see: dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/LoginPage.java +# see: dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/RedirectUrlValidatorTest.java +# f.e.: AllowedExternalRedirectionUrlAfterLogin=https://example.com +AllowedExternalRedirectionUrlAfterLogin= diff --git a/dataverse-webapp/src/main/webapp/WEB-INF/web.xml b/dataverse-webapp/src/main/webapp/WEB-INF/web.xml index c99442673f6..59039c9e961 100644 --- a/dataverse-webapp/src/main/webapp/WEB-INF/web.xml +++ b/dataverse-webapp/src/main/webapp/WEB-INF/web.xml @@ -175,6 +175,10 @@ COOKIE + + + edu.harvard.iq.dataverse.DataverseSessionConfigListener + eot diff --git a/dataverse-webapp/src/main/webapp/dataverse_footer.xhtml b/dataverse-webapp/src/main/webapp/dataverse_footer.xhtml index be141f6a3a1..ed3d0f861f6 100644 --- a/dataverse-webapp/src/main/webapp/dataverse_footer.xhtml +++ b/dataverse-webapp/src/main/webapp/dataverse_footer.xhtml @@ -37,6 +37,13 @@ + + + + + + +

diff --git a/dataverse-webapp/src/main/webapp/sitemap.xhtml b/dataverse-webapp/src/main/webapp/sitemap.xhtml index 0c5b0870f1e..62cbccca662 100644 --- a/dataverse-webapp/src/main/webapp/sitemap.xhtml +++ b/dataverse-webapp/src/main/webapp/sitemap.xhtml @@ -89,6 +89,13 @@ #{bundle['footer.accessibility.link.name']}

+ +

+ + + +

+
diff --git a/dataverse-webapp/src/main/webapp/uningest.xhtml b/dataverse-webapp/src/main/webapp/uningest.xhtml index b78b0fa0370..b2def5d0973 100644 --- a/dataverse-webapp/src/main/webapp/uningest.xhtml +++ b/dataverse-webapp/src/main/webapp/uningest.xhtml @@ -36,8 +36,20 @@ - + + + + + + + + + @@ -54,18 +66,17 @@ - - -
+ +
@@ -74,9 +85,7 @@

- - - +