diff --git a/README.md b/README.md index f00cfaf39..9a1578390 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Metacat: Data Preservation and Discovery System -Version: 2.15.1 Release +Version: 2.16.0 Release Send feedback and bugs to: metacat-dev@ecoinformatics.org http://github.com/NCEAS/metacat @@ -67,6 +67,14 @@ for the next release. ## Release Notes +### Release Notes for 2.16.0 +New features and bugs fixed in this release: +* Upgrade some library jar files to fix critical security threats +* Refactor the DOI service to use the plug-in architecture +* CN subjects cannot query private objects +* Users with the write permission cannot update system metadata +* Metacat should return a not-found error rather than the internal error when there is a typo in the old Metacat API url + ### Release Notes for 2.15.1 New features and bugs fixed in this release: * Metacat hangs with excessive thread counts diff --git a/build.properties b/build.properties index 417758897..5048b6006 100755 --- a/build.properties +++ b/build.properties @@ -2,7 +2,7 @@ #Version of this build. This needs to be a dotted numeric version. For #instance 1.9.1 is okay. 1.9.1_rc1 is not. -metacat.version=2.15.1 +metacat.version=2.16.0 #This is for packaging purposes. leave it blank for final production release. metacat.releaseCandidate= diff --git a/build.xml b/build.xml index 70dc9b547..cea23c204 100755 --- a/build.xml +++ b/build.xml @@ -32,7 +32,7 @@ - + diff --git a/lib/metacat.properties b/lib/metacat.properties index 66ddd6188..7fe60383b 100755 --- a/lib/metacat.properties +++ b/lib/metacat.properties @@ -34,7 +34,7 @@ server.internalPort=80 ############### Application Values ############ ## one of the few places where we use ANT tokens -application.metacatVersion=2.15.1 +application.metacatVersion=2.16.0 application.metacatReleaseInfo=-1 application.readOnlyMode=false @@ -135,6 +135,7 @@ database.upgradeVersion.2.14.0=upgrade-db-to-2.14.0 database.upgradeVersion.2.14.1=upgrade-db-to-2.14.1 database.upgradeVersion.2.15.0=upgrade-db-to-2.15.0 database.upgradeVersion.2.15.1=upgrade-db-to-2.15.1 +database.upgradeVersion.2.16.0=upgrade-db-to-2.16.0 ## for running java-based utilities database.upgradeUtility.1.5.0=edu.ucsb.nceas.metacat.admin.upgrade.Upgrade1_5_0 @@ -763,6 +764,7 @@ dataone.quotas.portal.namespaces=https://purl.dataone.org/portals-1.0.0;https:// dataone.session.appendLdapGroups.enabled=true ############# Global Identifiers Assignment Section ###################### +guid.doiservice.plugin.class=edu.ucsb.nceas.metacat.doi.ezid.EzidDOIService guid.assignGUIDs=false guid.ezid.enabled=false guid.ezid.username=apitest diff --git a/metacat-common/pom.xml b/metacat-common/pom.xml index 814e1b1f9..a17777073 100644 --- a/metacat-common/pom.xml +++ b/metacat-common/pom.xml @@ -4,7 +4,7 @@ edu.ucsb.nceas.metacat.common metacat-common jar - 2.15.1 + 2.16.0 metacat-common http://maven.apache.org @@ -219,17 +219,17 @@ org.apache.logging.log4j log4j-1.2-api - 2.14.0 + 2.16.0 org.apache.logging.log4j log4j-core - 2.14.0 + 2.16.0 org.apache.logging.log4j log4j-jcl - 2.14.0 + 2.16.0 commons-logging diff --git a/metacat-index/pom.xml b/metacat-index/pom.xml index b5583b81e..0b78dbf4e 100644 --- a/metacat-index/pom.xml +++ b/metacat-index/pom.xml @@ -4,19 +4,19 @@ edu.ucsb.nceas.metacat.index metacat-index war - 2.15.1 + 2.16.0 metacat-index http://maven.apache.org 2.3.14 - 2.15.1 + 2.16.0 dataone.org - http://maven.dataone.org + https://maven.dataone.org true @@ -77,7 +77,11 @@ org.slf4j slf4j-api - + + + log4j + log4j + diff --git a/pom.xml b/pom.xml index 1301b63e8..6721dbc19 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.ecoinformatics metacat - 2.15.1 + 2.16.0 metacat war http://maven.apache.org @@ -12,7 +12,7 @@ UTF-8 2.3.1 2.3.2 - 2.15.1 + 2.16.0 diff --git a/src/edu/ucsb/nceas/metacat/MetaCatServlet.java b/src/edu/ucsb/nceas/metacat/MetaCatServlet.java index 6118643dc..2d4a80b6c 100755 --- a/src/edu/ucsb/nceas/metacat/MetaCatServlet.java +++ b/src/edu/ucsb/nceas/metacat/MetaCatServlet.java @@ -749,17 +749,28 @@ private void handleGetOrPost(HttpServletRequest request, // attempt to redirect to metacatui (#view/{pid}) if not getting the raw XML // see: https://projects.ecoinformatics.org/ecoinfo/issues/6546 - if (!skin.equals("xml")) { + if (skin != null && !skin.equals("xml")) { String uiContext = PropertyService.getProperty("ui.context"); String docidNoRev = DocumentUtil.getSmartDocId(docidToRead); - int rev = DocumentUtil.getRevisionFromAccessionNumber(docidToRead); - String pid = null; - try { - pid = IdentifierManager.getInstance().getGUID(docidNoRev, rev); - response.sendRedirect(SystemUtil.getServerURL() + "/" + uiContext + "/#view/" + pid ); - return; - } catch (McdbDocNotFoundException nfe) { - logMetacat.warn("Could not locate PID for docid: " + docidToRead, nfe); + if (docidNoRev != null) { + int rev = DocumentUtil.getRevisionFromAccessionNumber(docidToRead); + String pid = null; + try { + pid = IdentifierManager.getInstance().getGUID(docidNoRev, rev); + response.sendRedirect(SystemUtil.getServerURL() + "/" + uiContext + "/#view/" + pid ); + return; + } catch (McdbDocNotFoundException nfe) { + logMetacat.warn("Could not locate PID for docid: " + docidToRead, nfe); + } + } else { + PrintWriter out = response.getWriter(); + response.setContentType("text/xml"); + out.println(""); + out.println(""); + out.println("The docid " + docidToRead + " doesn't match the format and doesn't exist."); + out.println(""); + out.close(); + return; } } diff --git a/src/edu/ucsb/nceas/metacat/admin/upgrade/UpdateDOI.java b/src/edu/ucsb/nceas/metacat/admin/upgrade/UpdateDOI.java index b1dcf0cce..157f3d830 100644 --- a/src/edu/ucsb/nceas/metacat/admin/upgrade/UpdateDOI.java +++ b/src/edu/ucsb/nceas/metacat/admin/upgrade/UpdateDOI.java @@ -37,9 +37,9 @@ import edu.ucsb.nceas.metacat.DocumentImpl; import edu.ucsb.nceas.metacat.IdentifierManager; import edu.ucsb.nceas.metacat.admin.AdminException; -import edu.ucsb.nceas.metacat.dataone.DOIService; import edu.ucsb.nceas.metacat.dataone.MNodeService; import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService; +import edu.ucsb.nceas.metacat.doi.DOIServiceFactory; import edu.ucsb.nceas.metacat.util.DocumentUtil; @@ -97,7 +97,7 @@ private void updateDOIRegistration(List identifiers) { //Update the registration if(sysMeta != null) { - DOIService.getInstance().registerDOI(sysMeta); + DOIServiceFactory.getDOIService().registerDOI(sysMeta); } } catch (Exception e) { // what to do? nothing diff --git a/src/edu/ucsb/nceas/metacat/dataone/D1AuthHelper.java b/src/edu/ucsb/nceas/metacat/dataone/D1AuthHelper.java index 7282cd2e3..1876da463 100644 --- a/src/edu/ucsb/nceas/metacat/dataone/D1AuthHelper.java +++ b/src/edu/ucsb/nceas/metacat/dataone/D1AuthHelper.java @@ -78,6 +78,7 @@ public class D1AuthHelper { private String notAuthorizedCode; private String serviceFailureCode; private Identifier requestIdentifier; + private static NodeList cnList = null; /** * Each instance should correspond to a single request. @@ -516,18 +517,22 @@ protected boolean checkExpandedPermissions(Session session, SystemMetadata sysme * @throws NotImplemented */ protected NodeList getCNNodeList() throws ServiceFailure { - - // are we allowed to do this? only CNs are allowed - try { - CNode cn = D1Client.getCN(); - - logMetacat.debug("getCNNodeList - got CN instance"); - return cn.listNodes(); - - } catch (NotImplemented e) { - logMetacat.error("Unexpected Error getting NodeList from getCNNodeList(). Got 'NotImplemented' from the service call!",e); - throw new ServiceFailure("","Could not get NodeList from the CN. got 'NotImplemented' from the service call!"); + if (cnList != null && cnList.getNodeList() != null && cnList.getNodeList().size() >0) { + logMetacat.debug("D1AuthHelper.getCNNodeList - got the cn list from the cache."); + return cnList; + } else { + // are we allowed to do this? only CNs are allowed + try { + CNode cn = D1Client.getCN(); + logMetacat.debug("D1AuthHelper.getCNNodeList - got CN instance and get the cn list from the network."); + cnList = cn.listNodes(); + return cnList; + } catch (NotImplemented e) { + logMetacat.error("Unexpected Error getting NodeList from getCNNodeList(). Got 'NotImplemented' from the service call!",e); + throw new ServiceFailure("","Could not get NodeList from the CN. got 'NotImplemented' from the service call!"); + } } + } /** diff --git a/src/edu/ucsb/nceas/metacat/dataone/D1NodeService.java b/src/edu/ucsb/nceas/metacat/dataone/D1NodeService.java index d56e3a87b..b9b8b3421 100644 --- a/src/edu/ucsb/nceas/metacat/dataone/D1NodeService.java +++ b/src/edu/ucsb/nceas/metacat/dataone/D1NodeService.java @@ -2585,6 +2585,52 @@ public void setUserAgent(String userAgent) { this.userAgent = userAgent; } + /** + * Check if the access control was modified between two system metadata objects. It compares two parts: + * RightsHolder and AccessPolicy + * @param originalSysmeta the original system metadata object + * @param newSysmeta the new system metadata object + * @return true if the access control was modified; false otherwise. + */ + public static boolean isAccessControlDirty(SystemMetadata originalSysmeta, SystemMetadata newSysmeta) { + boolean dirty = true; + if (originalSysmeta == null && newSysmeta == null) { + dirty = false; + logMetacat.debug("D1NodeService.isAccessControlDirty - is access control dirty?(both system metadata objects are null) - " + dirty); + } else if (originalSysmeta != null && newSysmeta != null) { + //first to check if the rights holder was changed. + Subject originalRightsHolder = originalSysmeta.getRightsHolder(); + Subject newRigthsHolder = newSysmeta.getRightsHolder(); + if (originalRightsHolder == null && newRigthsHolder == null) { + dirty = false; + logMetacat.debug("D1NodeService.isAccessControlDirty - is the right holder dirty?(both right holder objects are null) - " + dirty); + } else if (originalRightsHolder != null && newRigthsHolder != null) { + if (originalRightsHolder.compareTo(newRigthsHolder) == 0) { + dirty = false; + } else { + dirty = true; + } + logMetacat.debug("D1NodeService.isAccessControlDirty - is the right holder dirty?(both right holder objects are not null) - " + dirty + + " since the original right holder is " + originalRightsHolder.getValue() + + " and the new rights holder is " + newRigthsHolder.getValue()); + } else { + dirty = true; + logMetacat.debug("D1NodeService.isAccessControlDirty - is the rights holder dirty?(one rights holder object is null; another is not) - " + dirty); + } + if (!dirty) { + //rights holder is not changed, we need to compare the access policy + boolean isAccessPolicyEqual = equals(originalSysmeta.getAccessPolicy(), newSysmeta.getAccessPolicy()); + logMetacat.debug("D1NodeService.isAccessControlDirty - do the access policies equal?(we need to compare access policy since the rights hloders are same) - " + isAccessPolicyEqual); + dirty = !isAccessPolicyEqual; + } + logMetacat.debug("D1NodeService.isAccessControlDirty - is access control dirty?(both system metadata objects are not null) - " + dirty); + } else { + dirty = true; + logMetacat.debug("D1NodeService.isAccessControlDirty - is access control dirty?(one system metadata object is null; another is not) - " + dirty); + } + return dirty; + } + /** * Compare two AccessPolicy objects * @param ap1 @@ -2652,6 +2698,7 @@ public static boolean equals(AccessPolicy ap1, AccessPolicy ap2) { } } } + logMetacat.debug("D1NodeService.equals - does the two access policy object equal? - " + equal); return equal; } diff --git a/src/edu/ucsb/nceas/metacat/dataone/MNodeService.java b/src/edu/ucsb/nceas/metacat/dataone/MNodeService.java index 8336ce0f0..01086ae24 100644 --- a/src/edu/ucsb/nceas/metacat/dataone/MNodeService.java +++ b/src/edu/ucsb/nceas/metacat/dataone/MNodeService.java @@ -154,7 +154,6 @@ import org.ecoinformatics.datamanager.parser.generic.Eml200DataPackageParser; import org.w3c.dom.Document; -import edu.ucsb.nceas.ezid.EZIDException; import edu.ucsb.nceas.metacat.DBQuery; import edu.ucsb.nceas.metacat.DBTransform; import edu.ucsb.nceas.metacat.EventLog; @@ -170,6 +169,8 @@ import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService; import edu.ucsb.nceas.metacat.dataone.quota.QuotaServiceManager; import edu.ucsb.nceas.metacat.dataone.resourcemap.ResourceMapModifier; +import edu.ucsb.nceas.metacat.doi.DOIException; +import edu.ucsb.nceas.metacat.doi.DOIServiceFactory; import edu.ucsb.nceas.metacat.index.MetacatSolrEngineDescriptionHandler; import edu.ucsb.nceas.metacat.index.MetacatSolrIndex; import edu.ucsb.nceas.metacat.object.handler.NonXMLMetadataHandler; @@ -498,7 +499,7 @@ public Identifier update(Session session, Identifier pid, InputStream object, try { authDel.doUpdateAuth(session, existingSysMeta, Permission.WRITE, this.getCurrentNodeId()); //now the user has the write the permission. If the access rules in the new and old system metadata are the same, it is fine; otherwise, Metacat throws an exception - if (!D1NodeService.equals(sysmeta.getAccessPolicy(), existingSysMeta.getAccessPolicy())) { + if (D1NodeService.isAccessControlDirty(sysmeta, existingSysMeta)) { throw new NotAuthorized("1200", "Can't update the object with id " + pid.getValue() + " since the user try to change the access rules without the change permission: " + e.getDescription()); } allowed = true; @@ -707,7 +708,7 @@ public Identifier update(Session session, Identifier pid, InputStream object, // attempt to register the identifier - it checks if it is a doi try { - DOIService.getInstance().registerDOI(sysmeta); + DOIServiceFactory.getDOIService().registerDOI(sysmeta); } catch (Exception e) { throw new ServiceFailure("1190", "Could not register DOI: " + e.getMessage()); } @@ -831,7 +832,7 @@ public Identifier create(Session session, Identifier pid, InputStream object, Sy // attempt to register the identifier - it checks if it is a doi try { - DOIService.getInstance().registerDOI(sysmeta); + DOIServiceFactory.getDOIService().registerDOI(sysmeta); } catch (Exception e) { ServiceFailure sf = new ServiceFailure("1190", "Could not register DOI: " + e.getMessage()); sf.initCause(e); @@ -1911,7 +1912,7 @@ public boolean systemMetadataChanged(boolean needCheckAuthoriativeNode, Session if (currentLocalSysMeta.getSerialVersion().longValue() <= serialVersion ) { // attempt to re-register the identifier (it checks if it is a doi) try { - DOIService.getInstance().registerDOI(newSysMeta); + DOIServiceFactory.getDOIService().registerDOI(newSysMeta); } catch (Exception e) { logMetacat.warn("Could not [re]register DOI: " + e.getMessage(), e); } @@ -2014,8 +2015,8 @@ public Identifier generateIdentifier(Session session, String scheme, String frag } else if (scheme.equalsIgnoreCase(DOI_SCHEME)) { // generate a DOI try { - identifier = DOIService.getInstance().generateDOI(); - } catch (EZIDException e) { + identifier = DOIServiceFactory.getDOIService().generateDOI(); + } catch (Exception e) { ServiceFailure sf = new ServiceFailure("2191", "Could not generate DOI: " + e.getMessage()); sf.initCause(e); throw sf; @@ -2108,7 +2109,7 @@ public InputStream query(Session session, String engine, String query) throws In ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented, NotFound { Set subjects = getQuerySubjects(session); - boolean isMNadmin = isMNAdminQuery(session); + boolean isMNadmin = isMNOrCNAdminQuery(session); if (engine != null && engine.equals(EnabledQueryEngines.PATHQUERYENGINE)) { if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) { throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled."); @@ -2170,7 +2171,7 @@ public InputStream query(Session session, String engine, String query) throws In public InputStream postQuery(Session session, String engine, HashMap params) throws InvalidToken, ServiceFailure, NotAuthorized, InvalidRequest, NotImplemented, NotFound { Set subjects = getQuerySubjects(session); - boolean isMNadmin = isMNAdminQuery(session); + boolean isMNadmin = isMNOrCNAdminQuery(session); if (engine != null && engine.equals(EnabledQueryEngines.SOLRENGINE)) { if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.SOLRENGINE)) { throw new NotImplemented("0000", "MNodeService.query - the query engine "+engine +" hasn't been implemented or has been disabled."); @@ -2204,15 +2205,18 @@ private Set getQuerySubjects(Session session) { } /* - * Determine if the given session is a local admin subject. + * Determine if the given session is a local admin or cn subject. */ - private boolean isMNAdminQuery(Session session) throws ServiceFailure { + private boolean isMNOrCNAdminQuery(Session session) throws ServiceFailure { boolean isMNadmin= false; if (session != null && session.getSubject() != null) { D1AuthHelper authDel = new D1AuthHelper(request, null, "2822", "2821"); - if(authDel.isLocalMNAdmin(session)) { - logMetacat.debug("MNodeService.query - this is a mn admin session, it will bypass the access control rules."); + try { + authDel.doAdminAuthorization(session); + logMetacat.debug("MNodeService.isMNOrCNAdminQuery - this is a mn/cn admin session, it will bypass the access control rules."); isMNadmin=true;//bypass access rules since it is the admin + } catch (NotAuthorized e) { + logMetacat.debug("MNodeService.isMNOrCNAdminQuery - this is NOT a mn/cn admin session, it can't bypass the access control rules."); } } return isMNadmin; @@ -2956,13 +2960,25 @@ public boolean updateSystemMetadata(Session session, Identifier pid, if(currentSysmeta == null) { throw new InvalidRequest("4869", "We can't find the current system metadata on the member node for the id "+pid.getValue()); } + D1AuthHelper authDel = null; try { - D1AuthHelper authDel = new D1AuthHelper(request, pid, "4861","4868"); + authDel = new D1AuthHelper(request, pid, "4861","4868"); authDel.doUpdateAuth(session, currentSysmeta, Permission.CHANGE_PERMISSION, this.getCurrentNodeId()); } catch(ServiceFailure e) { throw new ServiceFailure("4868", "Can't determine if the client has the permission to update the system metacat of the object with id "+pid.getValue()+" since "+e.getDescription()); } catch(NotAuthorized e) { - throw new NotAuthorized("4861", "Can't update the system metacat of the object with id "+pid.getValue()+" since "+e.getDescription()); + //the user doesn't have the change permission. However, if it has the write permission and doesn't modify the access rules, Metacat still allows it to update the system metadata + try { + authDel.doUpdateAuth(session, currentSysmeta, Permission.WRITE, this.getCurrentNodeId()); + //now the user has the write the permission. If the access rules in the new and old system metadata are the same, it is fine; otherwise, Metacat throws an exception + if (D1NodeService.isAccessControlDirty(sysmeta, currentSysmeta)) { + throw new NotAuthorized("4861", "Can't update the system metadata of the object with id " + pid.getValue() + " since the user try to change the access rules without the change permission: " + e.getDescription()); + } + } catch(ServiceFailure ee) { + throw new ServiceFailure("4868", "Can't determine if the client has the permission to update the system metadata the object with id " + pid.getValue() + " since " + ee.getDescription()); + } catch(NotAuthorized ee) { + throw new NotAuthorized("4861", "Can't update the system metadata of object with id " + pid.getValue() + " since " + ee.getDescription()); + } } Date currentModiDate = currentSysmeta.getDateSysMetadataModified(); Date commingModiDate = sysmeta.getDateSysMetadataModified(); @@ -3026,7 +3042,7 @@ public void run() { // attempt to re-register the identifier (it checks if it is a doi) try { logMetacat.info("MNodeSerice.updateSystemMetadata - register doi if the pid "+sys.getIdentifier().getValue()+" is a doi"); - DOIService.getInstance().registerDOI(sys); + DOIServiceFactory.getDOIService().registerDOI(sys); } catch (Exception e) { logMetacat.warn("Could not [re]register DOI: " + e.getMessage(), e); } diff --git a/src/edu/ucsb/nceas/metacat/doi/DOIException.java b/src/edu/ucsb/nceas/metacat/doi/DOIException.java new file mode 100644 index 000000000..a45974e2c --- /dev/null +++ b/src/edu/ucsb/nceas/metacat/doi/DOIException.java @@ -0,0 +1,34 @@ +/** + * Copyright: 2021 Regents of the University of California and the + * National Center for Ecological Analysis and Synthesis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package edu.ucsb.nceas.metacat.doi; + + +/** + * An exception that encapsulates errors from the DOI service. +*/ +public class DOIException extends Exception { + + /** + * Constructor + * @param msg the eror message + */ + public DOIException(String msg) { + super(msg); + } +} diff --git a/src/edu/ucsb/nceas/metacat/doi/DOIService.java b/src/edu/ucsb/nceas/metacat/doi/DOIService.java new file mode 100644 index 000000000..3e9e2ff9c --- /dev/null +++ b/src/edu/ucsb/nceas/metacat/doi/DOIService.java @@ -0,0 +1,54 @@ +/** + * Copyright: 2021 Regents of the University of California and the + * National Center for Ecological Analysis and Synthesis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package edu.ucsb.nceas.metacat.doi; + +import org.dataone.service.exceptions.InvalidRequest; +import org.dataone.service.exceptions.NotImplemented; +import org.dataone.service.exceptions.ServiceFailure; +import org.dataone.service.types.v1.Identifier; +import org.dataone.service.types.v2.SystemMetadata; + +import edu.ucsb.nceas.ezid.EZIDException; + +/** + * An interface for the DOI service + * @author tao + */ +public interface DOIService { + + /** + * Submits DOI metadata information about the object to DOI services + * @param sysMeta + * @return true if succeeded; false otherwise. + * @throws EZIDException + * @throws ServiceFailure + * @throws NotImplemented + * @throws InterruptedException + */ + public boolean registerDOI(SystemMetadata sysMeta) throws InvalidRequest, DOIException, NotImplemented, + ServiceFailure, InterruptedException; + + /** + * Generate a DOI using the DOI service as configured + * @return the identifier which was minted by the DOI service + * @throws EZIDException + * @throws InvalidRequest + */ + public Identifier generateDOI() throws DOIException, InvalidRequest; +} diff --git a/src/edu/ucsb/nceas/metacat/doi/DOIServiceFactory.java b/src/edu/ucsb/nceas/metacat/doi/DOIServiceFactory.java new file mode 100644 index 000000000..78c30d435 --- /dev/null +++ b/src/edu/ucsb/nceas/metacat/doi/DOIServiceFactory.java @@ -0,0 +1,55 @@ +/** + * Copyright: 2021 Regents of the University of California and the + * National Center for Ecological Analysis and Synthesis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package edu.ucsb.nceas.metacat.doi; + +import edu.ucsb.nceas.metacat.properties.PropertyService; +import edu.ucsb.nceas.utilities.PropertyNotFoundException; + +/** + * A factory class to initialize an instance of DOIService + * based on the configuration in the metacat.properties file + * @author tao + * + */ +public class DOIServiceFactory { + private static DOIService doiService = null; + + /** + * Get a singleton instance of DOIService + * @return the instance of DOIService + * @throws PropertyNotFoundException + * @throws ClassNotFoundException + * @throws IllegalAccessException + * @throws InstantiationException + */ + public static DOIService getDOIService() throws PropertyNotFoundException, InstantiationException, + IllegalAccessException, ClassNotFoundException { + if (doiService == null) { + synchronized(DOIServiceFactory.class) { + if (doiService == null) { + String className = PropertyService.getProperty("guid.doiservice.plugin.class"); + Object object = Class.forName(className).newInstance(); + doiService = (DOIService) object; + } + } + } + return doiService; + } + +} diff --git a/src/edu/ucsb/nceas/metacat/dataone/DOIService.java b/src/edu/ucsb/nceas/metacat/doi/ezid/EzidDOIService.java similarity index 82% rename from src/edu/ucsb/nceas/metacat/dataone/DOIService.java rename to src/edu/ucsb/nceas/metacat/doi/ezid/EzidDOIService.java index 65ae86078..02d6a0a6d 100644 --- a/src/edu/ucsb/nceas/metacat/dataone/DOIService.java +++ b/src/edu/ucsb/nceas/metacat/doi/ezid/EzidDOIService.java @@ -20,7 +20,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -package edu.ucsb.nceas.metacat.dataone; +package edu.ucsb.nceas.metacat.doi.ezid; import java.io.InputStream; import java.lang.Integer; @@ -66,6 +66,9 @@ import edu.ucsb.nceas.ezid.profile.ErcMissingValueCode; import edu.ucsb.nceas.ezid.profile.InternalProfile; import edu.ucsb.nceas.ezid.profile.InternalProfileValues; +import edu.ucsb.nceas.metacat.dataone.MNodeService; +import edu.ucsb.nceas.metacat.doi.DOIException; +import edu.ucsb.nceas.metacat.doi.DOIService; import edu.ucsb.nceas.metacat.doi.datacite.DataCiteMetadataFactory; import edu.ucsb.nceas.metacat.doi.datacite.DefaultDataCiteFactory; import edu.ucsb.nceas.metacat.properties.PropertyService; @@ -82,11 +85,11 @@ * * @author leinfelder */ -public class DOIService { +public class EzidDOIService implements DOIService { public static final String DATACITE = "datacite"; - private Log logMetacat = LogFactory.getLog(DOIService.class); + private Log logMetacat = LogFactory.getLog(EzidDOIService.class); private boolean doiEnabled = false; @@ -101,24 +104,16 @@ public class DOIService { private Date lastLogin = null; private long loginPeriod = 1 * 24 * 60 * 60 * 1000; - - private static DOIService instance = null; private static int PRIMARY_SHOULDER_INDEX = 1; private Vector dataCiteFactories = new Vector(); - public static DOIService getInstance() { - if (instance == null) { - instance = new DOIService(); - } - return instance; - } /** - * Constructor, private for singleton access + * Constructor */ - private DOIService() { + public EzidDOIService() { // for DOIs String ezidServiceBaseUrl = null; @@ -210,7 +205,7 @@ private void refreshLogin() throws EZIDException { * @throws NotImplemented * @throws InterruptedException */ - public boolean registerDOI(SystemMetadata sysMeta) throws InvalidRequest, EZIDException, NotImplemented, ServiceFailure, InterruptedException { + public boolean registerDOI(SystemMetadata sysMeta) throws InvalidRequest, DOIException, NotImplemented, ServiceFailure, InterruptedException { // only continue if we have the feature turned on if (doiEnabled) { @@ -235,16 +230,21 @@ public boolean registerDOI(SystemMetadata sysMeta) throws InvalidRequest, EZIDEx // only continue if this DOI identifier or sid is in our configured shoulder list if(identifierIsDOI || sidIsDOI) { - // finish the other part for the identifier if it is an DOI - if(identifierIsDOI) { - registerDOI(identifier, sysMeta); - } - // finish the other part for the sid if it is an DOI - if(sidIsDOI) { - registerDOI(sid, sysMeta); - } + try { + // finish the other part for the identifier if it is an DOI + if(identifierIsDOI) { + registerDOI(identifier, sysMeta); + } + // finish the other part for the sid if it is an DOI + if(sidIsDOI) { + registerDOI(sid, sysMeta); + } + } catch (EZIDException e) { + throw new DOIException(e.getMessage()); + } } - + } else { + throw new InvalidRequest("2193", "DOI scheme is not enabled at this node."); } return true; @@ -338,36 +338,38 @@ private String generateDataCiteXML(String identifier, SystemMetadata sysMeta) th * @throws EZIDException * @throws InvalidRequest */ - public Identifier generateDOI() throws EZIDException, InvalidRequest { - - - // only continue if we have the feature turned on - if (!doiEnabled) { - throw new InvalidRequest("2193", "DOI scheme is not enabled at this node."); - } - - // add only the minimal metadata required for this DOI - HashMap metadata = new HashMap(); - metadata.put(DataCiteProfile.TITLE.toString(), ErcMissingValueCode.UNKNOWN.toString()); - metadata.put(DataCiteProfile.CREATOR.toString(), ErcMissingValueCode.UNKNOWN.toString()); - metadata.put(DataCiteProfile.PUBLISHER.toString(), ErcMissingValueCode.UNKNOWN.toString()); - metadata.put(DataCiteProfile.PUBLICATION_YEAR.toString(), ErcMissingValueCode.UNKNOWN.toString()); - metadata.put(InternalProfile.STATUS.toString(), InternalProfileValues.RESERVED.toString()); - metadata.put(InternalProfile.EXPORT.toString(), InternalProfileValues.NO.toString()); - - // make sure we have a current login - this.refreshLogin(); - - // Make sure we have a primary shoulder configured (which should enable mint operations) - if (!shoulderMap.containsKey(new Integer(PRIMARY_SHOULDER_INDEX))) { - throw new InvalidRequest("2193", "DOI scheme is not enabled at this node because primary shoulder unconfigured."); - } - - // call the EZID service - String doi = ezid.mintIdentifier(shoulderMap.get(new Integer(PRIMARY_SHOULDER_INDEX)), metadata); - Identifier identifier = new Identifier(); - identifier.setValue(doi); - + public Identifier generateDOI() throws DOIException, InvalidRequest { + Identifier identifier = new Identifier(); + try { + // only continue if we have the feature turned on + if (!doiEnabled) { + throw new InvalidRequest("2193", "DOI scheme is not enabled at this node."); + } + + // add only the minimal metadata required for this DOI + HashMap metadata = new HashMap(); + metadata.put(DataCiteProfile.TITLE.toString(), ErcMissingValueCode.UNKNOWN.toString()); + metadata.put(DataCiteProfile.CREATOR.toString(), ErcMissingValueCode.UNKNOWN.toString()); + metadata.put(DataCiteProfile.PUBLISHER.toString(), ErcMissingValueCode.UNKNOWN.toString()); + metadata.put(DataCiteProfile.PUBLICATION_YEAR.toString(), ErcMissingValueCode.UNKNOWN.toString()); + metadata.put(InternalProfile.STATUS.toString(), InternalProfileValues.RESERVED.toString()); + metadata.put(InternalProfile.EXPORT.toString(), InternalProfileValues.NO.toString()); + + // make sure we have a current login + this.refreshLogin(); + + // Make sure we have a primary shoulder configured (which should enable mint operations) + if (!shoulderMap.containsKey(new Integer(PRIMARY_SHOULDER_INDEX))) { + throw new InvalidRequest("2193", "DOI scheme is not enabled at this node because primary shoulder unconfigured."); + } + + // call the EZID service + String doi = ezid.mintIdentifier(shoulderMap.get(new Integer(PRIMARY_SHOULDER_INDEX)), metadata); + + identifier.setValue(doi); + } catch (EZIDException e) { + throw new DOIException(e.getMessage()); + } return identifier; } diff --git a/src/edu/ucsb/nceas/metacat/service/ServiceService.java b/src/edu/ucsb/nceas/metacat/service/ServiceService.java index 718656c20..16e2c533f 100755 --- a/src/edu/ucsb/nceas/metacat/service/ServiceService.java +++ b/src/edu/ucsb/nceas/metacat/service/ServiceService.java @@ -89,8 +89,8 @@ public static ServiceService getInstance(ServletContext servletContext) { public static void registerService(String serviceName, BaseService service) throws ServiceException { if (serviceList.containsKey(serviceName)) { - throw new ServiceException("ServiceService.registerService - Service: " + serviceName + " is already registered." - + " Use ServiceService.reregister() to replace the service."); + logMetacat.info("ServiceService.registerService - The service: " + serviceName + " already exists and Metacat skips the registration."); + return; //skip the registration if the service already exist. } logMetacat.info("ServiceService.registerService - Registering Service: " + serviceName); serviceList.put(serviceName, service); diff --git a/src/edu/ucsb/nceas/metacat/util/DocumentUtil.java b/src/edu/ucsb/nceas/metacat/util/DocumentUtil.java index 29cf79c79..5f440051a 100755 --- a/src/edu/ucsb/nceas/metacat/util/DocumentUtil.java +++ b/src/edu/ucsb/nceas/metacat/util/DocumentUtil.java @@ -349,7 +349,9 @@ public static String getDocIdFromAccessionNumber(String accessionNumber) String docid = null; if (accessionNumber == null) { return docid; } int indexOfLastSeperator = accessionNumber.lastIndexOf(separator); - docid = accessionNumber.substring(0, indexOfLastSeperator); + if (indexOfLastSeperator > 0) { + docid = accessionNumber.substring(0, indexOfLastSeperator); + } logMetacat.debug("DocumentUtil.getDocIdFromAccessionNumber - after parsing accession number, docid is " + docid); return docid; diff --git a/src/loaddtdschema-postgres.sql b/src/loaddtdschema-postgres.sql index 8811b7bbe..4d86af86c 100755 --- a/src/loaddtdschema-postgres.sql +++ b/src/loaddtdschema-postgres.sql @@ -217,4 +217,4 @@ INSERT INTO xml_catalog (entry_type, public_id, system_id) SELECT 'Schema', 'htt INSERT INTO xml_catalog (entry_type, public_id, format_id) SELECT 'NonXML', 'science-on-schema.org/Dataset;ld+json', 'science-on-schema.org/Dataset;ld+json' WHERE NOT EXISTS (SELECT * FROM xml_catalog WHERE public_id='science-on-schema.org/Dataset;ld+json'); INSERT INTO db_version (version, status, date_created) - VALUES ('2.15.1',1,CURRENT_DATE); + VALUES ('2.16.0',1,CURRENT_DATE); diff --git a/src/upgrade-db-to-2.16.0-postgres.sql b/src/upgrade-db-to-2.16.0-postgres.sql new file mode 100644 index 000000000..8d9042e53 --- /dev/null +++ b/src/upgrade-db-to-2.16.0-postgres.sql @@ -0,0 +1,13 @@ +/* + * Ensure xml_catalog sequence is at table max + */ + +SELECT setval('xml_catalog_id_seq', (SELECT max(catalog_id) from xml_catalog)); + +/* + * update the database version + */ +UPDATE db_version SET status=0; + +INSERT INTO db_version (version, status, date_created) + VALUES ('2.16.0', 1, CURRENT_DATE); diff --git a/test/edu/ucsb/nceas/metacat/admin/upgrade/UpdateDOITest.java b/test/edu/ucsb/nceas/metacat/admin/upgrade/UpdateDOITest.java index 617787c57..5888fc7bc 100644 --- a/test/edu/ucsb/nceas/metacat/admin/upgrade/UpdateDOITest.java +++ b/test/edu/ucsb/nceas/metacat/admin/upgrade/UpdateDOITest.java @@ -39,9 +39,9 @@ import edu.ucsb.nceas.ezid.EZIDService; import edu.ucsb.nceas.metacat.dataone.D1NodeServiceTest; -import edu.ucsb.nceas.metacat.dataone.DOIService; import edu.ucsb.nceas.metacat.dataone.MNodeService; -import edu.ucsb.nceas.metacat.dataone.RegisterDOITest; +import edu.ucsb.nceas.metacat.doi.ezid.EzidDOIService; +import edu.ucsb.nceas.metacat.doi.ezid.RegisterDOITest; import edu.ucsb.nceas.metacat.properties.PropertyService; import junit.framework.Test; import junit.framework.TestSuite; @@ -110,10 +110,10 @@ public void testUpdate() throws Exception { metadata = ezid.getMetadata(publishedPID.getValue()); Thread.sleep(2000); count++; - } while ((metadata == null || metadata.get(DOIService.DATACITE) == null) && count < 30); + } while ((metadata == null || metadata.get(EzidDOIService.DATACITE) == null) && count < 30); //System.out.println("The doi on the identifier is "+publishedPID.getValue()); assertNotNull(metadata); - String result = metadata.get(DOIService.DATACITE); + String result = metadata.get(EzidDOIService.DATACITE); //System.out.println("the result is \n"+result); assertTrue(result.contains("Test EML package - public-readable from morpho")); assertTrue(result.contains(RegisterDOITest.creatorsStr)); @@ -156,10 +156,10 @@ public void testUpdate() throws Exception { metadata = ezid.getMetadata(publishedSID.getValue()); Thread.sleep(2000); count++; - } while ((metadata == null || metadata.get(DOIService.DATACITE) == null) && count < 30); + } while ((metadata == null || metadata.get(EzidDOIService.DATACITE) == null) && count < 30); assertNotNull(metadata); - String result = metadata.get(DOIService.DATACITE); + String result = metadata.get(EzidDOIService.DATACITE); //System.out.println("the result is \n"+result); assertTrue(result.contains("Test EML package - public-readable from morpho")); assertTrue(result.contains(RegisterDOITest.creatorsStr)); diff --git a/test/edu/ucsb/nceas/metacat/dataone/D1NodeServiceTest.java b/test/edu/ucsb/nceas/metacat/dataone/D1NodeServiceTest.java index f82ac5501..ea0295252 100644 --- a/test/edu/ucsb/nceas/metacat/dataone/D1NodeServiceTest.java +++ b/test/edu/ucsb/nceas/metacat/dataone/D1NodeServiceTest.java @@ -101,6 +101,7 @@ public static Test suite() suite.addTest(new D1NodeServiceTest("testIsValidIdentifier")); suite.addTest(new D1NodeServiceTest("testAddParaFromSkinProperties")); suite.addTest(new D1NodeServiceTest("testAccessPolicyEqual")); + suite.addTest(new D1NodeServiceTest("testIsAccessControlDirty")); return suite; } @@ -128,6 +129,8 @@ public D1Node getCNode() throws ClientSideException { MockReplicationMNode mNode = new MockReplicationMNode("http://replication.node.com"); nodeLocator.putNode(nodeRef, mNode); D1Client.setNodeLocator(nodeLocator ); + D1Node node = D1Client.getCN();//call this method can clear the default cn + System.out.println("in the D1NodeServiceTest set up................. the base url is for cn is " + node.getNodeBaseServiceUrl()); } @@ -140,8 +143,23 @@ public void tearDown() { } public void testExpandRighsHolder() throws Exception { - // set back to force it to use defaults - D1Client.setNodeLocator(null); + printTestHeader("testExpandRighsHolder"); + // set back to force it to use defaults + NodeLocator nodeLocator1 = new NodeLocator() { + @Override + public D1Node getCNode() throws ClientSideException { + D1Node node = null; + try { + node = D1Client.getCN("https://cn.dataone.org/cn"); + } catch (Exception e) { + throw new ClientSideException(e.getMessage()); + } + return node; + } + }; + D1Client.setNodeLocator(nodeLocator1); + D1Node node = D1Client.getCN();//call this method can clear the mock cn + System.out.println("in the testExpandRighsHolder, ---the base url is for cn is " + node.getNodeBaseServiceUrl()); Subject rightsHolder = new Subject(); rightsHolder.setValue("CN=arctic-data-admins,DC=dataone,DC=org"); Subject user = new Subject(); @@ -312,6 +330,7 @@ public Session getCNSession() throws Exception { break; } } + System.out.println("the subject " + subject.getValue() + " was created for cn session----------------------- "); session.setSubject(subject); return session; @@ -715,5 +734,113 @@ public void testAccessPolicyEqual() throws Exception { assertTrue(ap2.getAllow(1).getSubject(1).getValue().equals("CN=Bryce Mecum A27576,O=Google,C=US,DC=cilogon,DC=org")); assertTrue(ap2.getAllow(1).getPermission(0).equals(Permission.CHANGE_PERMISSION)); } + + /** + * Test the isAccessControlDirty method + * @throws Exception + */ + public void testIsAccessControlDirty() throws Exception { + printTestHeader("testIsAccessControlDirty"); + String[] subjectsPublic = {"public"}; + String[] subjectsMix = {"http://orcid.org/0000-0002-8121-2341", "CN=Bryce Mecum A27576,O=Google,C=US,DC=cilogon,DC=org"}; + String rightsHolder1 = "http://orcid.org/0000-0002-8121-2341"; + String rightsHolder2 = "uid=tao,o=NCEAS,dc=ecoinformatics,dc=org"; + String rightsHolder3 = "UID=tao,O=NCEAS,DC=ecoinformatics,DC=org"; + Permission[] permissionsREAD = {Permission.READ}; + Permission[] permissionsCHANGE = {Permission.CHANGE_PERMISSION}; + + Subject sbjRightsHolder1 = new Subject(); + sbjRightsHolder1.setValue(rightsHolder1); + Subject sbjRightsHolder2 = new Subject(); + sbjRightsHolder2.setValue(rightsHolder2); + Subject sbjRightsHolder3 = new Subject(); + sbjRightsHolder3.setValue(rightsHolder3); + AccessPolicy ap1 = null; + AccessPolicy ap2 = null; + ap1 = AccessUtil.createSingleRuleAccessPolicy(subjectsPublic, permissionsREAD); + ap2 = AccessUtil.createSingleRuleAccessPolicy(subjectsMix, permissionsCHANGE); + + //the rights holder changes but access policy doesn't change + SystemMetadata sysmeta1 = new SystemMetadata(); + sysmeta1.setRightsHolder(sbjRightsHolder1); + sysmeta1.setAccessPolicy(ap1); + SystemMetadata sysmeta2 = new SystemMetadata(); + sysmeta2.setRightsHolder(sbjRightsHolder2); + sysmeta2.setAccessPolicy(ap1); + assertTrue(D1NodeService.isAccessControlDirty(sysmeta1, sysmeta2)); + + //both the rights holder and access policy change + sysmeta1 = new SystemMetadata(); + sysmeta1.setRightsHolder(sbjRightsHolder1); + sysmeta1.setAccessPolicy(ap1); + sysmeta2 = new SystemMetadata(); + sysmeta2.setRightsHolder(sbjRightsHolder2); + sysmeta2.setAccessPolicy(ap2); + assertTrue(D1NodeService.isAccessControlDirty(sysmeta1, sysmeta2)); + + //both the rights holder and access policy doesn't change + sysmeta1 = new SystemMetadata(); + sysmeta1.setRightsHolder(sbjRightsHolder1); + sysmeta1.setAccessPolicy(ap1); + sysmeta2 = new SystemMetadata(); + sysmeta2.setRightsHolder(sbjRightsHolder1); + sysmeta2.setAccessPolicy(ap1); + assertTrue(!D1NodeService.isAccessControlDirty(sysmeta1, sysmeta2)); + + sysmeta1 = new SystemMetadata(); + sysmeta1.setRightsHolder(sbjRightsHolder2); + sysmeta1.setAccessPolicy(ap1); + sysmeta2 = new SystemMetadata(); + sysmeta2.setRightsHolder(sbjRightsHolder3); //just change the letter case + sysmeta2.setAccessPolicy(ap1); + assertTrue(!D1NodeService.isAccessControlDirty(sysmeta1, sysmeta2)); + + //the rights holder doesn't change but the access policy changes + sysmeta1 = new SystemMetadata(); + sysmeta1.setRightsHolder(sbjRightsHolder1); + sysmeta1.setAccessPolicy(ap1); + sysmeta2 = new SystemMetadata(); + sysmeta2.setRightsHolder(sbjRightsHolder1); + sysmeta2.setAccessPolicy(ap2); + assertTrue(D1NodeService.isAccessControlDirty(sysmeta1, sysmeta2)); + + //some scenario with null values + sysmeta1 = null; + sysmeta2 = null; + assertTrue(!D1NodeService.isAccessControlDirty(sysmeta1, sysmeta2)); + + sysmeta1 = null; + sysmeta2 = new SystemMetadata(); + sysmeta2.setRightsHolder(sbjRightsHolder1); + sysmeta2.setAccessPolicy(ap2); + assertTrue(D1NodeService.isAccessControlDirty(sysmeta1, sysmeta2)); + + //without rights holder + sysmeta1 = new SystemMetadata(); + sysmeta1.setAccessPolicy(ap1); + sysmeta2 = new SystemMetadata(); + sysmeta2.setAccessPolicy(ap1); + assertTrue(!D1NodeService.isAccessControlDirty(sysmeta1, sysmeta2)); + + sysmeta1 = new SystemMetadata(); + sysmeta1.setAccessPolicy(ap1); + sysmeta2 = new SystemMetadata(); + sysmeta2.setAccessPolicy(ap2); + assertTrue(D1NodeService.isAccessControlDirty(sysmeta1, sysmeta2)); + + sysmeta1 = new SystemMetadata(); + sysmeta1.setAccessPolicy(ap1); + sysmeta2 = new SystemMetadata(); + sysmeta2.setRightsHolder(sbjRightsHolder1); + sysmeta2.setAccessPolicy(ap1); + assertTrue(D1NodeService.isAccessControlDirty(sysmeta1, sysmeta2)); + + sysmeta1 = new SystemMetadata(); + sysmeta1.setRightsHolder(sbjRightsHolder1); + sysmeta1.setAccessPolicy(ap1); + sysmeta2 = new SystemMetadata(); + sysmeta2.setAccessPolicy(ap1); + assertTrue(D1NodeService.isAccessControlDirty(sysmeta1, sysmeta2)); + } } diff --git a/test/edu/ucsb/nceas/metacat/dataone/MNodeAccessControlTest.java b/test/edu/ucsb/nceas/metacat/dataone/MNodeAccessControlTest.java index 6043618df..83fb531c7 100644 --- a/test/edu/ucsb/nceas/metacat/dataone/MNodeAccessControlTest.java +++ b/test/edu/ucsb/nceas/metacat/dataone/MNodeAccessControlTest.java @@ -304,7 +304,8 @@ private void testMethodsWithGivenHightsHolder(Session rightsHolderSession, Subje testIsAuthorized(PISCOManager, id1,Permission.READ,true); testIsAuthorized(nullSession, id1,Permission.READ,true); - //7. Test the updateSystemMetadata (needs change permission) (the public and submitter has the read permission and the knb-admin group has write permission) + //7. Test the updateSystemMetadata (needs change permission if the access control changed; otherwise the write permssion is enough) + //(the public and submitter has the read permission and the knb-admin group has write permission) //add a new policy that pisco group and submitter has the change permission, and third user has the read permission AccessRule rule4= new AccessRule(); rule4.addPermission(Permission.CHANGE_PERMISSION); @@ -334,7 +335,7 @@ private void testMethodsWithGivenHightsHolder(Session rightsHolderSession, Subje testUpdateSystemmetadata(publicSession, id1, sysmeta, false); testUpdateSystemmetadata(submitter, id1, sysmeta, true); testUpdateSystemmetadata(PISCOManager, id1, sysmeta, true); - testUpdateSystemmetadata(KNBadmin, id1, sysmeta, false); + testUpdateSystemmetadata(KNBadmin, id1, sysmeta, true); testUpdateSystemmetadata(rightsHolderSession, id1, sysmeta, true); testUpdateSystemmetadata(getCNSession(), id1, sysmeta, true); testUpdateSystemmetadata(getMNSession(), id1, sysmeta, true); diff --git a/test/edu/ucsb/nceas/metacat/dataone/MNodeQueryTest.java b/test/edu/ucsb/nceas/metacat/dataone/MNodeQueryTest.java index 45aca550f..35ecb8aee 100644 --- a/test/edu/ucsb/nceas/metacat/dataone/MNodeQueryTest.java +++ b/test/edu/ucsb/nceas/metacat/dataone/MNodeQueryTest.java @@ -708,6 +708,20 @@ public void testQueryAccessControlAgainstPrivateObject() throws Exception { resultStr = IOUtils.toString(stream, "UTF-8"); assertTrue(resultStr.contains(""+guid.getValue()+"")); assertTrue(resultStr.contains("false")); + + //CN session + Session cnSession = getCNSession(); + stream = MNodeService.getInstance(request).query(cnSession, "solr", query); + resultStr = IOUtils.toString(stream, "UTF-8"); + System.out.println("the guid is "+guid.getValue()); + System.out.println("the string is +++++++++++++++++++++++++++++++++++\n"+resultStr); + assertTrue(resultStr.contains(""+guid.getValue()+"")); + assertTrue(resultStr.contains("false")); + //postquery + stream = MNodeService.getInstance(request).postQuery(cnSession, "solr", params); + resultStr = IOUtils.toString(stream, "UTF-8"); + assertTrue(resultStr.contains(""+guid.getValue()+"")); + assertTrue(resultStr.contains("false")); } /** @@ -814,6 +828,20 @@ public void testQueryAccessControlAgainstPublicObject() throws Exception { resultStr = IOUtils.toString(stream, "UTF-8"); assertTrue(resultStr.contains(""+guid.getValue()+"")); assertTrue(resultStr.contains("false")); + + //CN session + Session cnSession = getCNSession(); + stream = MNodeService.getInstance(request).query(cnSession, "solr", query); + resultStr = IOUtils.toString(stream, "UTF-8"); + System.out.println("the guid is "+guid.getValue()); + System.out.println("the string is +++++++++++++++++++++++++++++++++++\n"+resultStr); + assertTrue(resultStr.contains(""+guid.getValue()+"")); + assertTrue(resultStr.contains("false")); + //postquery + stream = MNodeService.getInstance(request).postQuery(cnSession, "solr", params); + resultStr = IOUtils.toString(stream, "UTF-8"); + assertTrue(resultStr.contains(""+guid.getValue()+"")); + assertTrue(resultStr.contains("false")); } diff --git a/test/edu/ucsb/nceas/metacat/dataone/MNodeServiceTest.java b/test/edu/ucsb/nceas/metacat/dataone/MNodeServiceTest.java index ecbe692a8..8e461c77c 100644 --- a/test/edu/ucsb/nceas/metacat/dataone/MNodeServiceTest.java +++ b/test/edu/ucsb/nceas/metacat/dataone/MNodeServiceTest.java @@ -214,6 +214,7 @@ public static Test suite() { suite.addTest(new MNodeServiceTest("testAllowList")); suite.addTest(new MNodeServiceTest("testInsertJson_LD")); suite.addTest(new MNodeServiceTest("testCreateAndUpdateEventLog")); + suite.addTest(new MNodeServiceTest("testUpdateSystemMetadataPermission")); return suite; } @@ -796,6 +797,23 @@ public void testUpdate() { } catch (Exception ee) { assertTrue( ee instanceof NotAuthorized); } + + //the write user fails to update the object since it modified the rights holder (need the change permission) + guid21 = new Identifier(); + guid21.setValue("testUpdatewithAccessChange21." + System.currentTimeMillis()); + object = new ByteArrayInputStream("test".getBytes("UTF-8")); + updatedSysMeta = createSystemMetadata(guid21, session.getSubject(), object); + updatedSysMeta.getAccessPolicy().addAllow(writeRule); + updatedSysMeta.getAccessPolicy().addAllow(changeRule); + Subject newRightsHolder = new Subject(); + newRightsHolder.setValue("foo"); + updatedSysMeta.setRightsHolder(newRightsHolder); + try { + MNodeService.getInstance(request).update(writeSession, guid20, object, guid21, updatedSysMeta); + fail("The write-permission-only user can't change the rights holder"); + } catch (Exception ee) { + assertTrue( ee instanceof NotAuthorized); + } //the write user can update the object without modifying access rules object = new ByteArrayInputStream("test".getBytes("UTF-8")); @@ -809,8 +827,18 @@ public void testUpdate() { guid22.setValue("testUpdatewithAccessChange3." + System.currentTimeMillis()); object = new ByteArrayInputStream("test".getBytes("UTF-8")); updatedSysMeta = createSystemMetadata(guid22, session.getSubject(), object); + updatedSysMeta.getAccessPolicy().addAllow(changeRule); MNodeService.getInstance(request).update(changeSession, guid21, object, guid22, updatedSysMeta); + //the change user can update the rights holder + Identifier guid23 = new Identifier(); + guid23.setValue("testUpdatewithAccessChange4." + System.currentTimeMillis()); + object = new ByteArrayInputStream("test".getBytes("UTF-8")); + updatedSysMeta = createSystemMetadata(guid23, session.getSubject(), object); + updatedSysMeta.getAccessPolicy().addAllow(changeRule); + updatedSysMeta.setRightsHolder(newRightsHolder); + MNodeService.getInstance(request).update(changeSession, guid22, object, guid23, updatedSysMeta); + //test update an object with modified authoritative member node guid.setValue("testUpdate2." + System.currentTimeMillis()); object = new ByteArrayInputStream("test".getBytes("UTF-8")); @@ -1949,7 +1977,7 @@ else if (entry.getName().contains("data.2")) { // clean up bagFile.delete(); Identifier doi = MNodeService.getInstance(request).publish(session, metadataId); - Thread.sleep(30000); + Thread.sleep(60000); System.out.println("+++++++++++++++++++ the metadataId on the ore package is "+metadataId.getValue()); List oreIds = MNodeService.getInstance(request).lookupOreFor(session, doi, true); assertTrue(oreIds.size() == 1); @@ -2871,6 +2899,145 @@ public void testUpdateSystemMetadata() throws Exception { } } + /** + * Test the updateSystemmetadata method by users with different permission + * @throws Exception + */ + public void testUpdateSystemMetadataPermission() throws Exception { + Subject read = new Subject(); + read.setValue("Read"); + Session readSession = new Session(); + readSession.setSubject(read); + AccessRule readRule = new AccessRule(); + readRule.addPermission(Permission.READ); + readRule.addSubject(read); + + Subject write = new Subject(); + write.setValue("Write"); + Session writeSession = new Session(); + writeSession.setSubject(write); + AccessRule writeRule = new AccessRule(); + writeRule.addPermission(Permission.WRITE); + writeRule.addSubject(write); + + Subject change = new Subject(); + change.setValue("Change"); + Session changeSession = new Session(); + changeSession.setSubject(change); + AccessRule changeRule = new AccessRule(); + changeRule.addPermission(Permission.CHANGE_PERMISSION); + changeRule.addSubject(change); + + Subject rightsHolder = new Subject(); + rightsHolder.setValue("rightsHolder"); + Subject newRightsHolder = new Subject(); + newRightsHolder.setValue("newRightsHolder"); + + String str1 = "object1"; + Thread.sleep(1000); + //insert test documents with a series id + Session session = getTestSession(); + Identifier guid = new Identifier(); + guid.setValue(generateDocumentId()); + InputStream object1 = new ByteArrayInputStream(str1.getBytes("UTF-8")); + SystemMetadata sysmeta = createSystemMetadata(guid, session.getSubject(), object1); + sysmeta.setRightsHolder(rightsHolder); + AccessPolicy policy = new AccessPolicy(); + policy.addAllow(readRule); + policy.addAllow(writeRule); + policy.addAllow(changeRule); + sysmeta.setAccessPolicy(policy); + MNodeService.getInstance(request).create(session, guid, object1, sysmeta); + SystemMetadata readSys = MNodeService.getInstance(request).getSystemMetadata(readSession, guid); + assertTrue(readSys.getAccessPolicy().sizeAllowList() == 3); + + //Read permission user can't update system metadata + try { + MNodeService.getInstance(request).updateSystemMetadata(readSession, guid, sysmeta); + fail("We shouldn't get there"); + } catch (Exception e) { + assertTrue(e instanceof NotAuthorized); + } + readSys = MNodeService.getInstance(request).getSystemMetadata(readSession, guid); + assertTrue(readSys.getAccessPolicy().sizeAllowList() == 3); + + //Write permission user can't update the right holder + object1 = new ByteArrayInputStream(str1.getBytes("UTF-8")); + SystemMetadata newSysmeta = createSystemMetadata(guid, session.getSubject(), object1); + newSysmeta.setRightsHolder(newRightsHolder); + AccessPolicy policy1 = new AccessPolicy(); + policy1.addAllow(readRule); + policy1.addAllow(writeRule); + policy1.addAllow(changeRule); + newSysmeta.setAccessPolicy(policy1); + try { + MNodeService.getInstance(request).updateSystemMetadata(writeSession, guid, newSysmeta); + fail("We shouldn't get there"); + } catch (Exception e) { + assertTrue(e instanceof NotAuthorized); + } + + readSys = MNodeService.getInstance(request).getSystemMetadata(readSession, guid); + assertTrue(readSys.getAccessPolicy().sizeAllowList() == 3); + + //Write permission user can't update the access policy + newSysmeta.setRightsHolder(rightsHolder); + AccessPolicy policy2 = new AccessPolicy(); + policy2.addAllow(readRule); + newSysmeta.setAccessPolicy(policy2); + try { + MNodeService.getInstance(request).updateSystemMetadata(writeSession, guid, newSysmeta); + fail("We shouldn't get there"); + } catch (Exception e) { + assertTrue(e instanceof NotAuthorized); + } + + //Write permission user can update file name + readSys = MNodeService.getInstance(request).getSystemMetadata(readSession, guid); + assertTrue(readSys.getAccessPolicy().sizeAllowList() == 3); + newSysmeta.setRightsHolder(rightsHolder); + newSysmeta.setAccessPolicy(policy); + newSysmeta.setFileName("foo"); + newSysmeta.setDateSysMetadataModified(readSys.getDateSysMetadataModified()); + newSysmeta.setDateUploaded(readSys.getDateUploaded()); + MNodeService.getInstance(request).updateSystemMetadata(writeSession, guid, newSysmeta); + readSys = MNodeService.getInstance(request).getSystemMetadata(readSession, guid); + assertTrue(readSys.getFileName().equals("foo")); + assertTrue(readSys.getAccessPolicy().sizeAllowList() == 3); + assertTrue(readSys.getRightsHolder().getValue().equals("rightsHolder")); + + //Change permission user can update the right holder + newSysmeta.setRightsHolder(newRightsHolder); + newSysmeta.setAccessPolicy(policy); + newSysmeta.setDateSysMetadataModified(readSys.getDateSysMetadataModified()); + MNodeService.getInstance(request).updateSystemMetadata(changeSession, guid, newSysmeta); + readSys = MNodeService.getInstance(request).getSystemMetadata(readSession, guid); + assertTrue(readSys.getRightsHolder().getValue().equals("newRightsHolder")); + assertTrue(readSys.getFileName().equals("foo")); + assertTrue(readSys.getAccessPolicy().sizeAllowList() == 3); + + //Change permission user can update the access policy + policy2 = new AccessPolicy(); + policy2.addAllow(readRule); + policy2.addAllow(changeRule); + newSysmeta.setAccessPolicy(policy2); + newSysmeta.setDateSysMetadataModified(readSys.getDateSysMetadataModified()); + MNodeService.getInstance(request).updateSystemMetadata(changeSession, guid, newSysmeta); + readSys = MNodeService.getInstance(request).getSystemMetadata(readSession, guid); + assertTrue(readSys.getRightsHolder().getValue().equals("newRightsHolder")); + assertTrue(readSys.getFileName().equals("foo")); + assertTrue(readSys.getAccessPolicy().sizeAllowList() == 2); + + //change permission user can update file name + newSysmeta.setFileName("newfoo"); + newSysmeta.setDateSysMetadataModified(readSys.getDateSysMetadataModified()); + MNodeService.getInstance(request).updateSystemMetadata(changeSession, guid, newSysmeta); + readSys = MNodeService.getInstance(request).getSystemMetadata(readSession, guid); + assertTrue(readSys.getFileName().equals("newfoo")); + assertTrue(readSys.getRightsHolder().getValue().equals("newRightsHolder")); + assertTrue(readSys.getAccessPolicy().sizeAllowList() == 2); + } + public void testUpdateObsoletesAndObsoletedBy() throws Exception { String str1 = "object1"; String str2 = "object2"; @@ -3860,6 +4027,7 @@ public void testCreateAndUpdateEventLog() throws Exception { assertTrue(!result.next()); result.close(); + Thread.sleep(10000); Identifier guid8 = new Identifier(); guid8.setValue("isoTestCreateAndUpdateEventLog2." + System.currentTimeMillis()); object = new FileInputStream(new File("test/isoTestNodc1.xml")); diff --git a/test/edu/ucsb/nceas/metacat/doi/DOIServiceFactoryTest.java b/test/edu/ucsb/nceas/metacat/doi/DOIServiceFactoryTest.java new file mode 100644 index 000000000..097e8014f --- /dev/null +++ b/test/edu/ucsb/nceas/metacat/doi/DOIServiceFactoryTest.java @@ -0,0 +1,38 @@ +/** + * Copyright: 2021 Regents of the University of California and the + * National Center for Ecological Analysis and Synthesis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package edu.ucsb.nceas.metacat.doi; + +import org.junit.Test; + +import edu.ucsb.nceas.MCTestCase; +import edu.ucsb.nceas.metacat.properties.PropertyService; + +public class DOIServiceFactoryTest extends MCTestCase { + + /** + * Test the method of getInstance + * @throws Exception + */ + @Test + public void testGetDOIService() throws Exception { + String className = PropertyService.getProperty("guid.doiservice.plugin.class"); + DOIService service = DOIServiceFactory.getDOIService(); + assertTrue(service.getClass().getCanonicalName().equals(className)); + } +} diff --git a/test/edu/ucsb/nceas/metacat/dataone/MultipleDOIShouldersTest.java b/test/edu/ucsb/nceas/metacat/doi/ezid/MultipleDOIShouldersTest.java similarity index 98% rename from test/edu/ucsb/nceas/metacat/dataone/MultipleDOIShouldersTest.java rename to test/edu/ucsb/nceas/metacat/doi/ezid/MultipleDOIShouldersTest.java index df21342d6..f2d11bf26 100644 --- a/test/edu/ucsb/nceas/metacat/dataone/MultipleDOIShouldersTest.java +++ b/test/edu/ucsb/nceas/metacat/doi/ezid/MultipleDOIShouldersTest.java @@ -18,7 +18,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -package edu.ucsb.nceas.metacat.dataone; +package edu.ucsb.nceas.metacat.doi.ezid; import java.io.ByteArrayInputStream; import java.io.FileInputStream; @@ -38,6 +38,9 @@ import edu.ucsb.nceas.ezid.EZIDService; import edu.ucsb.nceas.ezid.profile.DataCiteProfile; import edu.ucsb.nceas.ezid.profile.InternalProfile; +import edu.ucsb.nceas.metacat.dataone.D1NodeServiceTest; +import edu.ucsb.nceas.metacat.dataone.MNodeService; +import edu.ucsb.nceas.metacat.dataone.MockCNode; import edu.ucsb.nceas.metacat.properties.PropertyService; import junit.framework.Test; import junit.framework.TestSuite; diff --git a/test/edu/ucsb/nceas/metacat/dataone/RegisterDOITest.java b/test/edu/ucsb/nceas/metacat/doi/ezid/RegisterDOITest.java similarity index 98% rename from test/edu/ucsb/nceas/metacat/dataone/RegisterDOITest.java rename to test/edu/ucsb/nceas/metacat/doi/ezid/RegisterDOITest.java index 802acf44f..114674deb 100644 --- a/test/edu/ucsb/nceas/metacat/dataone/RegisterDOITest.java +++ b/test/edu/ucsb/nceas/metacat/doi/ezid/RegisterDOITest.java @@ -23,7 +23,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -package edu.ucsb.nceas.metacat.dataone; +package edu.ucsb.nceas.metacat.doi.ezid; import java.io.ByteArrayInputStream; @@ -55,7 +55,11 @@ import edu.ucsb.nceas.ezid.EZIDService; import edu.ucsb.nceas.ezid.profile.DataCiteProfile; import edu.ucsb.nceas.ezid.profile.InternalProfile; +import edu.ucsb.nceas.metacat.dataone.D1NodeServiceTest; +import edu.ucsb.nceas.metacat.dataone.MNodeService; +import edu.ucsb.nceas.metacat.dataone.MockCNode; import edu.ucsb.nceas.metacat.doi.datacite.EML2DataCiteFactoryTest; +import edu.ucsb.nceas.metacat.doi.ezid.EzidDOIService; import edu.ucsb.nceas.metacat.properties.PropertyService; /** @@ -299,8 +303,8 @@ public void testCreateDOI() { } while (metadata == null && count < MAX_TIMES); assertNotNull(metadata); - assertTrue(metadata.containsKey(DOIService.DATACITE)); - String datacite = metadata.get(DOIService.DATACITE); + assertTrue(metadata.containsKey(EzidDOIService.DATACITE)); + String datacite = metadata.get(EzidDOIService.DATACITE); System.out.println(""+datacite); assertTrue(datacite.contains("CN=Benjamin Leinfelder A515,O=University of Chicago,C=US,DC=cilogon,DC=org")); } catch (Exception e) { @@ -365,7 +369,7 @@ public void testPublishDOI() { } while (metadata == null && count < MAX_TIMES); assertNotNull(metadata); - String result = metadata.get(DOIService.DATACITE); + String result = metadata.get(EzidDOIService.DATACITE); System.out.println("result is\n"+result); Node node = MNodeService.getInstance(null).getCapabilities(); String nodeName = node.getName(); @@ -437,7 +441,7 @@ public void tesCreateDOIinSid() { } while (metadata == null && count < MAX_TIMES); System.out.println("The doi on the identifier is "+publishedIdentifier.getValue()); assertNotNull(metadata); - String result = metadata.get(DOIService.DATACITE); + String result = metadata.get(EzidDOIService.DATACITE); System.out.println("the result is \n"+result); assertTrue(result.contains("Test EML package - public-readable from morpho")); assertTrue(result.contains(creatorsStr)); @@ -488,7 +492,7 @@ public void tesCreateDOIinSid() { } while (metadata == null && count < MAX_TIMES); assertNotNull(metadata); - String result = metadata.get(DOIService.DATACITE); + String result = metadata.get(EzidDOIService.DATACITE); System.out.println("the result is \n"+result); assertTrue(result.contains("Test EML package - public-readable from morpho")); assertTrue(result.contains(creatorsStr)); @@ -538,7 +542,7 @@ public void tesCreateDOIinSid() { } while (metadata == null && count < MAX_TIMES); assertNotNull(metadata); - String result = metadata.get(DOIService.DATACITE); + String result = metadata.get(EzidDOIService.DATACITE); System.out.println("the result is \n"+result); assertTrue(result.contains("Test EML package - public-readable from morpho")); assertTrue(result.contains(creatorsStr)); @@ -562,7 +566,7 @@ public void tesCreateDOIinSid() { } while (metadata2 == null && count < MAX_TIMES); assertNotNull(metadata2); - result = metadata.get(DOIService.DATACITE); + result = metadata.get(EzidDOIService.DATACITE); System.out.println("the result is \n"+result); assertTrue(result.contains("Test EML package - public-readable from morpho")); assertTrue(result.contains(creatorsStr)); @@ -899,7 +903,7 @@ public void testPublishEML220() throws Exception { count++; } while (metadata == null && count < MAX_TIMES); assertNotNull(metadata); - String result = metadata.get(DOIService.DATACITE); + String result = metadata.get(EzidDOIService.DATACITE); assertTrue(result.contains("EML Annotation Example")); assertTrue(result.contains("0000-0002-1209-5122")); assertTrue(result.contains("It can include multiple paragraphs"));