package org.dataone.integration.it.testImplementations; import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; import org.apache.commons.io.IOUtils; import org.dataone.client.RetryHandler; import org.dataone.client.auth.AuthTokenSession; import org.dataone.client.v1.itk.D1Object; import org.dataone.client.v1.types.D1TypeBuilder; import org.dataone.integration.ContextAwareTestCaseDataone; import org.dataone.integration.ContextAwareTestCaseDataone.LogContents; import org.dataone.integration.ExampleUtilities; import org.dataone.integration.adapters.CNCallAdapter; import org.dataone.integration.adapters.MNCallAdapter; import org.dataone.integration.it.ContextAwareAdapter; import org.dataone.integration.webTest.WebTestDescription; import org.dataone.integration.webTest.WebTestName; import org.dataone.portal.TokenGenerator; import org.dataone.service.exceptions.BaseException; import org.dataone.service.exceptions.NotFound; 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.Node; import org.dataone.service.types.v1.NodeType; import org.dataone.service.types.v1.Permission; import org.dataone.service.types.v1.Person; import org.dataone.service.types.v1.ReplicationPolicy; import org.dataone.service.types.v1.Service; import org.dataone.service.types.v1.Session; import org.dataone.service.types.v1.SubjectInfo; import org.dataone.service.types.v2.NodeList; import org.dataone.service.types.v2.SystemMetadata; import org.dataone.service.types.v2.TypeFactory; import org.dataone.service.util.Constants; public class AuthTokenTestImplementation extends ContextAwareAdapter { private static final int MAX_SYNC_WAIT = 15 * 60 * 1000; // 15 minutes private static final String SAMPLE_ORCID = "http://orcid.org/0000-0002-1825-0097"; public AuthTokenTestImplementation(ContextAwareTestCaseDataone catc) { super(catc); } private Session getTokenSesssion(String userId, String fullName) { String token = null; try { token = TokenGenerator.getInstance().getJWT(userId, fullName); } catch (Exception e) { throw new AssertionError("Unable to get a token for (" + userId + ", " + fullName + "). " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage(), e); } Session session = null; try { session = TokenGenerator.getInstance().getSession(token); } catch (IOException e) { throw new AssertionError("Unable to get a session for token (" + userId + ", " + fullName + "). " + "got IOException : " + e.getMessage(), e); } if (session instanceof AuthTokenSession) log.info("Created auth token: " + ((AuthTokenSession) session).getAuthToken()); return session; } @WebTestName("CN.echoCredentials with a token") @WebTestDescription("tests that echoCredintials can be called successfully with " + "an auth token (and doesn't yield something like an InvalidToken exception)") public void testEchoCredentials(Iterator nodeIterator, String version) { while (nodeIterator.hasNext()) testEchoCredentials(nodeIterator.next(), version); } public void testEchoCredentials(Node node, String version) { String userId = SAMPLE_ORCID; String fullName = "Jane Scientist"; Session tokenSession = getTokenSesssion(userId, fullName); // calls will override subject with tokenSession CNCallAdapter cn = new CNCallAdapter(getSession(cnSubmitter), node, version); String currentUrl = node.getBaseURL(); printTestHeader("testEchoCredentials(...) vs. node: " + currentUrl); try { SubjectInfo subjectInfo = cn.echoCredentials(tokenSession); for(Person p : subjectInfo.getPersonList()) log.info("credentials subject :" + p.getSubject()); } catch (BaseException e) { throw new AssertionError("echoCredentials failed with token (" + userId + ", " + fullName + "). " + "got " + e.getClass().getSimpleName() + " [" + e.getCode() + "," + e.getDetail_code() + "] : " + e.getMessage() + " from " + cn.getLatestRequestUrl(), e); } catch (Exception e) { throw new AssertionError("echoCredentials failed with token (" + userId + ", " + fullName + "). " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage() + " from " + cn.getLatestRequestUrl(), e); } } @WebTestName("MN.create with token") @WebTestDescription("tests that creating an object on the MN is possible " + "with a token") public void testMnCreate(Iterator nodeIterator, String version) { while (nodeIterator.hasNext()) testMnCreate(nodeIterator.next(), version); } public void testMnCreate(Node node, String version) { final CNCallAdapter cn = new CNCallAdapter(getSession(cnSubmitter), node, version); MNCallAdapter mn = null; try { NodeList nodeList = cn.listNodes(); for (Node n : nodeList.getNodeList()) { if (n.getType() != NodeType.MN) continue; try { MNCallAdapter mnCaller = new MNCallAdapter(getSession(cnSubmitter), n, "v2"); mnCaller.ping(); Node capabilities = mnCaller.getCapabilities(); for (Service s : capabilities.getServices().getServiceList()) { if (s.getVersion().equalsIgnoreCase("v2")) { mn = mnCaller; break; } } if (mn != null) break; } catch (Exception e1) { continue; } } } catch (Exception e) { throw new AssertionError("testCnQuery - test setup failed! ", e); } String userId = SAMPLE_ORCID; String fullName = "Jane Scientist"; Session tokenSession = getTokenSesssion(userId, fullName); String currentUrl = node.getBaseURL(); printTestHeader("testMnCreate(...) vs. node: " + currentUrl); Object[] dataPackage; try { dataPackage = ExampleUtilities.generateTestSciDataPackage( "testMnCreate_", true, userId); } catch (Exception e) { throw new AssertionError("Unable to generate a test object! " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage(), e); } org.dataone.service.types.v1.SystemMetadata sysmetaV1 = (org.dataone.service.types.v1.SystemMetadata) dataPackage[2]; final Identifier pid = (Identifier) dataPackage[0]; SystemMetadata sysmeta; try { sysmeta = TypeFactory.convertTypeFromType(sysmetaV1,SystemMetadata.class); } catch (Exception e) { throw new AssertionError("Unable to convert v1 sysmeta to v2 sysmeta. " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage(), e); } try { mn.create(tokenSession, pid, (InputStream) dataPackage[1], sysmeta); } catch (Exception e) { throw new AssertionError("Unable to create object (" + pid.getValue() + ") with token (" + userId + ", " + fullName + "). " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage() + " from " + mn.getLatestRequestUrl(), e); } // MN.create() so need to wait for CN sync try { RetryHandler cnGetSysmetaHandler = new RetryHandler() { @Override protected SystemMetadata attempt() throws TryAgainException, Exception { try { log.info("attempting CN getSystemMEtadata..."); return cn.getSystemMetadata(null, pid); } catch (NotFound | ServiceFailure e) { TryAgainException f = new TryAgainException(); f.initCause(e); throw f; } } }; cnGetSysmetaHandler.execute(30*1000, MAX_SYNC_WAIT); } catch (Exception e) { throw new AssertionError("testCnQuery: Unable to fetch sysmeta from CN. Check status of CN sync. " + cn.getLatestRequestUrl() + " for pid " + pid.getValue() + ", Created on " + mn.getNodeBaseServiceUrl(), e); } try { mn.isAuthorized(tokenSession, pid, Permission.READ); } catch (BaseException e) { throw new AssertionError("isAuthorized failed for object (" + pid.getValue() + ") with token (" + userId + ", " + fullName + "). " + "got " + e.getClass().getSimpleName() + " [" + e.getCode() + "," + e.getDetail_code() + "] : " + e.getMessage() + " from " + mn.getLatestRequestUrl(), e); } catch (Exception e) { throw new AssertionError("isAuthorized failed for object (" + pid.getValue() + ") with token (" + userId + ", " + fullName + "). " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage() + " from " + mn.getLatestRequestUrl(), e); } } @WebTestName("CN.isAuthorized with token") @WebTestDescription("tests that creating an object on the CN with a token's subject " + "in the access policy, then using CN.isAuthorized succeeds " + "and returns true for that token") public void testCnIsAuthorized(Iterator nodeIterator, String version) { while (nodeIterator.hasNext()) testCnIsAuthorized(nodeIterator.next(), version); } public void testCnIsAuthorized(Node node, String version) { String userId = SAMPLE_ORCID; String fullName = "Jane Scientist"; Session tokenSession = getTokenSesssion(userId, fullName); if (tokenSession instanceof AuthTokenSession) log.info("Created auth token: " + ((AuthTokenSession) tokenSession).getAuthToken()); // calls will public subject with tokenSession CNCallAdapter cn = new CNCallAdapter(getSession(cnSubmitter), node, version); String currentUrl = node.getBaseURL(); printTestHeader("testCnIsAuthorized(...) vs. node: " + currentUrl); AccessRule accessRule = new AccessRule(); accessRule.addSubject(tokenSession.getSubject()); accessRule.addPermission(Permission.READ); ReplicationPolicy replPolicy = new ReplicationPolicy(); replPolicy.setReplicationAllowed(false); replPolicy.setNumberReplicas(0); Identifier pid = D1TypeBuilder.buildIdentifier("testCnIsAuthorized_token_8"); try { catc.procureTestObject(cn, accessRule, pid, cnSubmitter, userId, replPolicy); } catch (Exception e) { throw new AssertionError("Unable to create object (" + pid.getValue() + "), " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage() + " from " + cn.getLatestRequestUrl(), e); } // CN.create() so no need to wait for sync try { cn.isAuthorized(tokenSession, pid, Permission.READ); } catch (BaseException e) { throw new AssertionError("isAuthorized failed for object (" + pid.getValue() + ") with token (" + userId + ", " + fullName + "). " + "got " + e.getClass().getSimpleName() + " [" + e.getCode() + "," + e.getDetail_code() + "] : " + e.getMessage() + " from " + cn.getLatestRequestUrl(), e); } catch (Exception e) { throw new AssertionError("isAuthorized failed for object (" + pid.getValue() + ") with token (" + userId + ", " + fullName + "). " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage() + " from " + cn.getLatestRequestUrl(), e); } } @WebTestName("MN.isAuthorized with token") @WebTestDescription("tests that creating an object then using " + "MN.isAuthorized succeeds and returns true for that token") public void testMnIsAuthorized(Iterator nodeIterator, String version) { while (nodeIterator.hasNext()) testMnIsAuthorized(nodeIterator.next(), version); } public void testMnIsAuthorized(Node node, String version) { String userId = SAMPLE_ORCID; String fullName = "Jane Scientist"; Session tokenSession = getTokenSesssion(userId, fullName); // calls will override subject with tokenSession MNCallAdapter mn = new MNCallAdapter(getSession(cnSubmitter), node, version); String currentUrl = node.getBaseURL(); printTestHeader("testMnIsAuthorized(...) vs. node: " + currentUrl); AccessRule accessRule = new AccessRule(); accessRule.addSubject(tokenSession.getSubject()); accessRule.addPermission(Permission.READ); AccessPolicy policy = new AccessPolicy(); policy.addAllow(accessRule); ReplicationPolicy replPolicy = new ReplicationPolicy(); replPolicy.setReplicationAllowed(false); replPolicy.setNumberReplicas(0); Identifier pid = D1TypeBuilder.buildIdentifier("testMnIsAuthorized_token_8"); try { catc.procureTestObject(mn, accessRule, pid, cnSubmitter, userId, replPolicy); } catch (Exception e) { throw new AssertionError("Unable to create object (" + pid.getValue() + "), " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage() + " from " + mn.getLatestRequestUrl(), e); } try { mn.isAuthorized(tokenSession, pid, Permission.READ); } catch (BaseException e) { throw new AssertionError("isAuthorized failed for object (" + pid.getValue() + ") with token (" + userId + ", " + fullName + "). " + "got " + e.getClass().getSimpleName() + " [" + e.getCode() + "," + e.getDetail_code() + "] : " + e.getMessage() + " from " + mn.getLatestRequestUrl(), e); } catch (Exception e) { throw new AssertionError("isAuthorized failed for object (" + pid.getValue() + ") with token (" + userId + ", " + fullName + "). " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage() + " from " + mn.getLatestRequestUrl(), e); } } @WebTestName("MN.update with token") @WebTestDescription("tests that creating an object with an auth token, then " + "using MN.update with the token succeeds") public void testMnUpdate(Iterator nodeIterator, String version) { while (nodeIterator.hasNext()) testMnUpdate(nodeIterator.next(), version); } public void testMnUpdate(Node node, String version) { String userId = SAMPLE_ORCID; String fullName = "Jane Scientist"; Session tokenSession = getTokenSesssion(userId, fullName); // calls will override subject with tokenSession MNCallAdapter mn = new MNCallAdapter(getSession(cnSubmitter), node, version); String currentUrl = node.getBaseURL(); printTestHeader("testMnUpdate(...) vs. node: " + currentUrl); AccessRule accessRule = new AccessRule(); accessRule.addSubject(tokenSession.getSubject()); accessRule.addPermission(Permission.WRITE); AccessPolicy policy = new AccessPolicy(); policy.addAllow(accessRule); AccessRule accessRule2 = new AccessRule(); accessRule2.addSubject(D1TypeBuilder.buildSubject(Constants.SUBJECT_PUBLIC)); accessRule2.addPermission(Permission.READ); policy.addAllow(accessRule2); ReplicationPolicy replPolicy = new ReplicationPolicy(); replPolicy.setReplicationAllowed(false); replPolicy.setNumberReplicas(0); // create Identifier oldPid = D1TypeBuilder.buildIdentifier("testMnUpdate_token_8_" + ExampleUtilities.generateIdentifier()); SystemMetadata oldSysmeta = null; // InputStream oldObjInputStream = null; // // try { // byte[] contentBytes = ExampleUtilities.getExampleObjectOfType(ContextAwareTestCaseDataone.DEFAULT_TEST_OBJECTFORMAT); // D1Object d1o = new D1Object(oldPid, contentBytes, // D1TypeBuilder.buildFormatIdentifier(ContextAwareTestCaseDataone.DEFAULT_TEST_OBJECTFORMAT), // tokenSession.getSubject(), // node.getIdentifier()); // oldSysmeta = TypeFactory.convertTypeFromType(d1o.getSystemMetadata(), SystemMetadata.class); // oldSysmeta.setAuthoritativeMemberNode(oldSysmeta.getAuthoritativeMemberNode()); // oldSysmeta.setAccessPolicy(policy); // oldSysmeta.setSubmitter(tokenSession.getSubject()); // oldObjInputStream = new ByteArrayInputStream(contentBytes); // // mn.create(tokenSession, oldPid, oldObjInputStream, oldSysmeta); // } catch (Exception e) { // throw new AssertionError("Unable to create old object (" + oldPid.getValue() + "), " // + "got " + e.getClass().getSimpleName() + " : " + e.getMessage() // + " from " + mn.getLatestRequestUrl(), e); // } try { // catc.createTestObject(mn, oldPid, policy, userId, userId, replPolicy); catc.createTestObject(mn, oldPid, policy, cnSubmitter, userId, replPolicy); } catch (Exception e) { throw new AssertionError("Unable to create object (" + oldPid.getValue() + "), " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage() + " from " + mn.getLatestRequestUrl(), e); } // get sysmeta try { Thread.sleep(10000); // metacat writes to db asynchronously, wait 10s oldSysmeta = mn.getSystemMetadata(null, oldPid); } catch (Exception e) { throw new AssertionError("Unable to get sysmeta for created object (" + oldPid.getValue() + "), " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage() + " from " + mn.getLatestRequestUrl(), e); } // create update object Identifier newPid = D1TypeBuilder.buildIdentifier("testMnUpdate_token_8_" + ExampleUtilities.generateIdentifier()); SystemMetadata newSysmeta = null; InputStream objectInputStream = null; try { byte[] contentBytes = ExampleUtilities.getExampleObjectOfType(ContextAwareTestCaseDataone.DEFAULT_TEST_OBJECTFORMAT); D1Object d1o = new D1Object(newPid, contentBytes, D1TypeBuilder.buildFormatIdentifier(ContextAwareTestCaseDataone.DEFAULT_TEST_OBJECTFORMAT), tokenSession.getSubject(), oldSysmeta.getAuthoritativeMemberNode()); newSysmeta = TypeFactory.convertTypeFromType(d1o.getSystemMetadata(), SystemMetadata.class); newSysmeta.setAuthoritativeMemberNode(oldSysmeta.getAuthoritativeMemberNode()); newSysmeta.setObsoletes(oldPid); newSysmeta.setAccessPolicy(policy); newSysmeta.setSubmitter(tokenSession.getSubject()); objectInputStream = new ByteArrayInputStream(contentBytes); } catch (Exception e) { throw new AssertionError("creating object for MN.update() failed for object (" + newPid.getValue() + ") with token (" + userId + ", " + fullName + "). " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage() + " from " + mn.getLatestRequestUrl(), e); } // update try { mn.update(tokenSession, oldPid, objectInputStream, newPid, newSysmeta); } catch (BaseException e) { throw new AssertionError("update failed for object (" + oldPid.getValue() + ") with token (" + userId + ", " + fullName + "). " + "got " + e.getClass().getSimpleName() + " [" + e.getCode() + "," + e.getDetail_code() + "] : " + e.getMessage() + " from " + mn.getLatestRequestUrl(), e); } catch (Exception e) { throw new AssertionError("update failed for object (" + oldPid.getValue() + ") with token (" + userId + ", " + fullName + "). " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage() + " from " + mn.getLatestRequestUrl(), e); } } @WebTestName("CN.query with token") @WebTestDescription("tests that creating an object and then using " + "CN.query can locate the object") public void testCnQuery(Iterator nodeIterator, String version) { while (nodeIterator.hasNext()) testCnQuery(nodeIterator.next(), version); } public void testCnQuery(Node node, String version) { final CNCallAdapter cn = new CNCallAdapter(getSession(cnSubmitter), node, version); MNCallAdapter mn = null; try { NodeList nodeList = cn.listNodes(); for (Node n : nodeList.getNodeList()) { if (n.getType() != NodeType.MN) continue; try { MNCallAdapter mnCaller = new MNCallAdapter(getSession(cnSubmitter), n, "v2"); mnCaller.ping(); Node capabilities = mnCaller.getCapabilities(); for (Service s : capabilities.getServices().getServiceList()) { if (s.getVersion().equalsIgnoreCase("v2")) { mn = mnCaller; break; } } if (mn != null) break; } catch (Exception e1) { continue; } } } catch (Exception e) { throw new AssertionError("testCnQuery - test setup failed! ", e); } assertTrue("testCnQuery - test setup needs to be able to locate a v2 MN!", mn != null); String userId = SAMPLE_ORCID; String fullName = "Jane Scientist"; Session tokenSession = getTokenSesssion(userId, fullName); if (tokenSession instanceof AuthTokenSession) log.info("Created auth token: " + ((AuthTokenSession) tokenSession).getAuthToken()); // calls will public subject with tokenSession String currentUrl = node.getBaseURL(); printTestHeader("testCnQuery(...) vs. node: " + currentUrl); AccessRule accessRule = new AccessRule(); accessRule.addSubject(tokenSession.getSubject()); accessRule.addPermission(Permission.READ); ReplicationPolicy replPolicy = new ReplicationPolicy(); replPolicy.setReplicationAllowed(false); replPolicy.setNumberReplicas(0); final Identifier pid = D1TypeBuilder.buildIdentifier("testCnQuery_token_8"); try { catc.procureTestObject(mn, accessRule, pid, cnSubmitter, userId, replPolicy); } catch (Exception e) { throw new AssertionError("Unable to create object (" + pid.getValue() + "), " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage() + " from " + mn.getLatestRequestUrl(), e); } // MN.create() so need to wait for CN sync try { RetryHandler cnGetSysmetaHandler = new RetryHandler() { @Override protected SystemMetadata attempt() throws TryAgainException, Exception { try { log.info("attempting CN getSystemMEtadata..."); return cn.getSystemMetadata(null, pid); } catch (NotFound | ServiceFailure e) { TryAgainException f = new TryAgainException(); f.initCause(e); throw f; } } }; cnGetSysmetaHandler.execute(30*1000, MAX_SYNC_WAIT); } catch (Exception e) { throw new AssertionError("testCnQuery: Unable to fetch sysmeta from CN. Check status of CN sync. " + cn.getLatestRequestUrl() + " for pid " + pid.getValue() + ", Created on " + mn.getNodeBaseServiceUrl(), e); } // CN.query InputStream is = null; try { is = cn.query(tokenSession, "solr", "?q=identifier:" + pid.getValue()); log.info("CN.query ran against " + cn.getLatestRequestUrl() + " for pid " + pid.getValue()); LogContents numQueryContents = ContextAwareTestCaseDataone.getNumQueryContents(is); log.info("CN.query results have a count of " + numQueryContents.existingLogs + " logs " + "and contain " + numQueryContents.docsReturned + " returned docs."); assertTrue("CN.query resutls should have a non-zero count for pid " + pid.getValue() + " against CN " + cn.getLatestRequestUrl(), numQueryContents.existingLogs > 0); } catch (BaseException e) { throw new AssertionError("query failed for object (" + pid.getValue() + ") with token (" + userId + ", " + fullName + "). " + "got " + e.getClass().getSimpleName() + " [" + e.getCode() + "," + e.getDetail_code() + "] : " + e.getMessage() + " from " + cn.getLatestRequestUrl(), e); } catch (Exception e) { throw new AssertionError("query failed for object (" + pid.getValue() + ") with token (" + userId + ", " + fullName + "). " + "got " + e.getClass().getSimpleName() + " : " + e.getMessage() + " from " + cn.getLatestRequestUrl(), e); } finally { IOUtils.closeQuietly(is); } } }