package edu.ucsb.nceas.metacat.dataone; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathFactory; import junit.framework.Test; import junit.framework.TestSuite; import org.apache.http.HttpResponse; import org.dataone.client.v2.itk.D1Client; import org.dataone.client.v2.MNode; import org.dataone.client.v2.formats.ObjectFormatCache; import org.dataone.client.rest.RestClient; import org.dataone.client.auth.CertificateManager; import org.dataone.configuration.Settings; import org.dataone.service.exceptions.ServiceFailure; import org.dataone.service.types.v1.AccessPolicy; import org.dataone.service.types.v1.AccessRule; import org.dataone.service.types.v1.Identifier; import org.dataone.service.types.v1.Permission; import org.dataone.service.types.v1.Person; import org.dataone.service.types.v1.Session; import org.dataone.service.types.v1.Subject; import org.dataone.service.types.v1.SubjectInfo; import org.dataone.service.types.v2.SystemMetadata; import org.dataone.service.util.Constants; import org.junit.Before; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; /** * A test class to test the access filter mechanism for the solr query * @author tao * */ public class SolrQueryAccessFilterTest extends D1NodeServiceTest { private static final String SOLR = "solr"; private static final String EML201NAMESPACE = "eml://ecoinformatics.org/eml-2.0.1"; private static final String CREATEUSER = "CN=Christopher Jones A583,O=Google,C=US,DC=cilogon,DC=org"; private static final String QUERYUSER = "CN=ben leinfelder A756,O=Google,C=US,DC=cilogon,DC=org"; private static final String GROUP1 = "CN=PISCO-data-managers,DC=cilogon,DC=org"; private static final String GROUP2 = "CN=dataone-coredev,DC=cilogon,DC=org"; private static final String USERWITHCERT = "CN=Jing Tao,OU=NCEAS,O=UCSB,ST=California,C=US"; private static final String EMLFILE = "test/restfiles/knb-lter-gce.109.6.xml"; private static final String INTRUSTCERTFILE = "test/test-credentials/test-user.pem"; private static final String IDXPATH = "//response/result/doc/str[@name='id']/text()"; private static final String TITLEPATH = "//response/result/doc/str[@name='title']/text()"; private static final String TITLE = "Mollusc population abundance monitoring: Fall 2000 mid-marsh and creekbank infaunal and epifaunal mollusc abundance based on collections from GCE marsh, monitoring sites 1-10"; /** * Build the test suite * @return */ public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest(new SolrQueryAccessFilterTest("testPublicReadable")); suite.addTest(new SolrQueryAccessFilterTest("testOnlyUserReadable")); suite.addTest(new SolrQueryAccessFilterTest("testGroupReadable")); suite.addTest(new SolrQueryAccessFilterTest("testOnlyRightHolderReadable")); suite.addTest(new SolrQueryAccessFilterTest("testDistrustCertificate")); return suite; } /** * Set up the test fixtures * * @throws Exception */ @Before public void setUp() throws Exception { super.setUp(); // set up the configuration for d1client Settings.getConfiguration().setProperty("D1Client.cnClassName", MockCNode.class.getName()); } /** * Constructor for the tests * * @param name - the name of the test */ public SolrQueryAccessFilterTest(String name) { super(name); } /** * Test to query a public readable document */ public void testPublicReadable() throws Exception { Session session = getSession(CREATEUSER, null); Identifier id = generateIdentifier(); String[] allowUsers = {Constants.SUBJECT_PUBLIC}; File object = new File(EMLFILE); SystemMetadata sysmeta = generateSystemMetadata(id, session.getSubject(), object , allowUsers); createObject(session, id, object, sysmeta); Thread.sleep(10000); Session querySession = getSession(Constants.SUBJECT_PUBLIC, null); InputStream input = query(querySession, id); Document doc = generateDoc(input); String resultId = extractElementValue(doc, IDXPATH); assertTrue("In the testPublicReadable method, the query result should have the id "+id.getValue()+ " rather than "+resultId, resultId.equals(id.getValue())); String title = extractElementValue(doc, TITLEPATH); assertTrue(title.equals(TITLE)); Session querySession2 = getSession(QUERYUSER, null); input = query(querySession2, id); doc = generateDoc(input); String resultId2 = extractElementValue(doc, IDXPATH); assertTrue("In the testPublicReadable method, the query result should have the id "+id.getValue()+ " rather than "+resultId2, resultId2.equals(id.getValue())); title = extractElementValue(doc, TITLEPATH); assertTrue(title.equals(TITLE)); archive(session, id); input = query(querySession2, id); doc = generateDoc(input); String resultId3 = extractElementValue(doc, IDXPATH); assertTrue("In the testPublicReadable method, the query result should be null since the document was archived. ", resultId3 == null); } /** * Test to query a document which can only be read by a specified user */ public void testOnlyUserReadable() throws Exception { Thread.sleep(15000); Session session = getSession(CREATEUSER, null); Identifier id = generateIdentifier(); String[] allowUsers = {QUERYUSER}; File object = new File(EMLFILE); SystemMetadata sysmeta = generateSystemMetadata(id, session.getSubject(), object , allowUsers); createObject(session, id, object, sysmeta); Thread.sleep(10000); Session querySession = getSession(Constants.SUBJECT_PUBLIC, null); InputStream input = query(querySession, id); Document doc = generateDoc(input); String resultId = extractElementValue(doc, IDXPATH); assertTrue("In the testOnlyUserReadable method, the query result id should be null for the public rather than "+resultId, resultId == null); Session querySession2 = getSession(QUERYUSER, null); input = query(querySession2, id); doc = generateDoc(input); resultId = extractElementValue(doc, IDXPATH); assertTrue("In the testOnlyUserReadable method, the query result for the user "+QUERYUSER+" should have the id "+id.getValue()+" rather than "+resultId, resultId.equals(id.getValue())); String title = extractElementValue(doc, TITLEPATH); assertTrue(title.equals(TITLE)); archive(session, id); } /** * Test to query a document which can be read by a specified group */ public void testGroupReadable() throws Exception { Thread.sleep(15000); Session session = getSession(CREATEUSER, null); Identifier id = generateIdentifier(); String[] allowUsers = {GROUP1, GROUP2}; File object = new File(EMLFILE); SystemMetadata sysmeta = generateSystemMetadata(id, session.getSubject(), object , allowUsers); createObject(session, id, object, sysmeta); Thread.sleep(10000); Session querySession = getSession(Constants.SUBJECT_PUBLIC, null); InputStream input = query(querySession, id); Document doc = generateDoc(input); String resultId = extractElementValue(doc, IDXPATH); assertTrue("In the testGroupReadable method, the query result id should be null for the public ", resultId == null); Session querySession2 = getSession(QUERYUSER, null); input = query(querySession2, id); doc = generateDoc(input); resultId = extractElementValue(doc, IDXPATH); assertTrue("In the testGroupReadable method, the query result for the user "+QUERYUSER+" which doesn't belong to the group should be null ", resultId == null); String[]groups = {GROUP1}; Session querySession3 = getSession(QUERYUSER, groups); input = query(querySession3, id); doc = generateDoc(input); resultId = extractElementValue(doc, IDXPATH); assertTrue("In the testGroupReadable method, the query result for the user "+QUERYUSER+" which belong to the group should have the id "+id.getValue(), resultId.equals(id.getValue())); String title = extractElementValue(doc, TITLEPATH); assertTrue(title.equals(TITLE)); archive(session, id); } /** * Test to query a document which only can be read by the rightHolder */ public void testOnlyRightHolderReadable() throws Exception { Thread.sleep(15000); Session session = getSession(CREATEUSER, null); Identifier id = generateIdentifier(); String[] allowUsers = null; File object = new File(EMLFILE); SystemMetadata sysmeta = generateSystemMetadata(id, session.getSubject(), object , allowUsers); createObject(session, id, object, sysmeta); Thread.sleep(10000); Session querySession = getSession(Constants.SUBJECT_PUBLIC, null); InputStream input = query(querySession, id); Document doc = generateDoc(input); String resultId = extractElementValue(doc, IDXPATH); assertTrue("In the testOnlyRightHolderReadable method, the query result id should be null for the public ", resultId == null); Session querySession2 = getSession(QUERYUSER, null); input = query(querySession2, id); doc = generateDoc(input); resultId = extractElementValue(doc, IDXPATH); assertTrue("In the testOnlyRightHolderReadable method, the query result for the user "+QUERYUSER+" which doesn't belong to the group should be null.", resultId == null); String[]groups = {GROUP1}; Session querySession3 = getSession(QUERYUSER, groups); input = query(querySession3, id); doc = generateDoc(input); resultId = extractElementValue(doc, IDXPATH); assertTrue("In the testOnlyRightHolderReadable method, the query result for the user "+QUERYUSER+" which belong to the group should be null.", resultId == null); Session querySession4 = getSession(CREATEUSER, groups); input = query(querySession4, id); doc = generateDoc(input); resultId = extractElementValue(doc, IDXPATH); assertTrue("In the testOnlyRightHolderReadable method, the query result for the creator "+CREATEUSER+" should be "+id.getValue(), id.getValue().equals(resultId)); String title = extractElementValue(doc, TITLEPATH); assertTrue(title.equals(TITLE)); archive(session, id); } /** * Test a user with a distrusted certificate. * @throws Exception */ public void testDistrustCertificate() throws Exception { //create a object only be readable by the USERWITHCERT Session session = getSession(CREATEUSER, null); Identifier id = generateIdentifier(); String[] allowUsers = {USERWITHCERT}; File object = new File(EMLFILE); SystemMetadata sysmeta = generateSystemMetadata(id, session.getSubject(), object , allowUsers); createObject(session, id, object, sysmeta); Thread.sleep(10000); //use faking session, the user can query the document Session querySession = getSession(USERWITHCERT, null); InputStream input = query(querySession, id); Document doc = generateDoc(input); String resultId = extractElementValue(doc, IDXPATH); assertTrue("In the testDistrustCertificate method, the query result id should be "+id.getValue(), id.getValue().equals(resultId)); String title = extractElementValue(doc, TITLEPATH); assertTrue(title.equals(TITLE)); //Use the libclient without the session, the user shouldn't query the document since its certificate is distrusted and it will be considered as the public. org.dataone.service.types.v2.Node node = MNodeService.getInstance(request).getCapabilities(); CertificateManager.getInstance().setCertificateLocation(INTRUSTCERTFILE); String baseURL = node.getBaseURL(); System.out.println("================The base url is "+baseURL); if (baseURL.contains("https://localhost")) { // force localhost to skip https - most common for devs baseURL = baseURL.replace("https", "http"); baseURL = baseURL.replace("8443", "8080"); baseURL = baseURL.replace("443", "80"); } System.out.println("================The MODIFIED base url is "+baseURL); MNode mnNode = D1Client.getMN(baseURL); try { input = mnNode.query(querySession, SOLR, generateQuery(id.getValue())); fail("Can't reach here since it is an untrusted certificate"); } catch (Exception e) { System.out.println("The exception is "+e.getMessage()); System.out.println("the exception class is "+e.getClass().getCanonicalName()); assertTrue(e instanceof ServiceFailure); } //doc = generateDoc(input); //String resultId2 = extractElementValue(doc, IDXPATH); //assertTrue("In the testDistrustCertificate method, the query result id should be null", resultId2==null); archive(session, id); } /* * constructs a "fake" session with the specified subject and groups. * If groups is not null, the session will have a subjectinfo which contains the person with the subject and is the member of the groups. * @return */ private Session getSession(String subjectValue, String[]groups) throws Exception { Session session = new Session(); Subject subject = new Subject(); subject.setValue(subjectValue); session.setSubject(subject); if(groups != null) { Person person = new Person(); person.setSubject(subject); person.setVerified(new Boolean(true)); ListgroupSubjects = new ArrayList(); for(String group: groups) { Subject groupSub = new Subject(); groupSub.setValue(group); groupSubjects.add(groupSub); } person.setIsMemberOfList(groupSubjects); SubjectInfo subjectInfo = new SubjectInfo(); subjectInfo.addPerson(person); session.setSubjectInfo(subjectInfo); } return session; } /* * Create a data object in the dataone server. * Return the identifier of the created object */ private void createObject(Session session, Identifier id, File object, SystemMetadata sysmeta) throws Exception { MNodeService.getInstance(request).create(session, id, new FileInputStream(object), sysmeta); } private Identifier generateIdentifier() { Identifier guid = new Identifier(); long random = Math.round(Math.random()*10000); guid.setValue("test." + System.currentTimeMillis()+(new Long(random)).toString()); return guid; } /* * Archive the given id. */ private void archive(Session session, Identifier id) throws Exception { MNodeService.getInstance(request).archive(session, id); } /* * Generate system metadata for the file */ private SystemMetadata generateSystemMetadata(Identifier id, Subject owner, File objectFile, String[] allowedSubjects) throws Exception{ SystemMetadata sysmeta = createSystemMetadata(id, owner, new FileInputStream(objectFile)); AccessPolicy accessPolicy = null; if(allowedSubjects != null && allowedSubjects.length >0) { accessPolicy = new AccessPolicy(); for(int i=0; i