Repository: incubator-sentry Updated Branches: refs/heads/master 3226ce992 -> 7d214749a
SENTRY-223: Add a test for updates with doc-level security (Gregory Chanan via Vamsee Yarlagadda) Project: http://git-wip-us.apache.org/repos/asf/incubator-sentry/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-sentry/commit/7d214749 Tree: http://git-wip-us.apache.org/repos/asf/incubator-sentry/tree/7d214749 Diff: http://git-wip-us.apache.org/repos/asf/incubator-sentry/diff/7d214749 Branch: refs/heads/master Commit: 7d214749a349567f258e3f83c554ae73cd5c0781 Parents: 3226ce9 Author: Vamsee <[email protected]> Authored: Wed May 21 18:21:20 2014 -0700 Committer: Vamsee <[email protected]> Committed: Wed May 21 18:21:20 2014 -0700 ---------------------------------------------------------------------- .../tests/e2e/solr/TestDocLevelOperations.java | 291 ++++++++++++++----- .../solr/sentry/test-authz-provider.ini | 6 +- 2 files changed, 220 insertions(+), 77 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7d214749/sentry-tests/sentry-tests-solr/src/test/java/org/apache/sentry/tests/e2e/solr/TestDocLevelOperations.java ---------------------------------------------------------------------- diff --git a/sentry-tests/sentry-tests-solr/src/test/java/org/apache/sentry/tests/e2e/solr/TestDocLevelOperations.java b/sentry-tests/sentry-tests-solr/src/test/java/org/apache/sentry/tests/e2e/solr/TestDocLevelOperations.java index d4307ec..31ecd5b 100644 --- a/sentry-tests/sentry-tests-solr/src/test/java/org/apache/sentry/tests/e2e/solr/TestDocLevelOperations.java +++ b/sentry-tests/sentry-tests-solr/src/test/java/org/apache/sentry/tests/e2e/solr/TestDocLevelOperations.java @@ -18,7 +18,6 @@ package org.apache.sentry.tests.e2e.solr; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.junit.After; import org.junit.Before; import static org.junit.Assert.assertEquals; @@ -27,6 +26,8 @@ import static org.junit.Assert.assertTrue; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.impl.CloudSolrServer; import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.client.solrj.response.UpdateResponse; + import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.SolrInputDocument; @@ -34,6 +35,7 @@ import org.apache.solr.common.SolrInputDocument; import java.io.File; import java.net.URLEncoder; import java.util.ArrayList; +import java.util.List; import org.junit.Test; @@ -70,12 +72,18 @@ public class TestDocLevelOperations extends AbstractSolrSentryTestBase { } /** - * Test that queries from different users only return the documents they have access to. + * Creates docs as follows and verifies queries work as expected: + * - creates NUM_DOCS documents, where the document id equals the order + * it was created in, starting at 0 + * - even-numbered documents get "junit_role" auth token + * - odd-numbered documents get "admin_role" auth token + * - all documents get some bogus auth tokens + * - all documents get a docLevel_role auth token */ - @Test - public void testDocLevelOperations() throws Exception { - String collectionName = "docLevelCollection"; - setupCollectionWithDocSecurity(collectionName); + private void createDocsAndQuerySimple(String collectionName) throws Exception { + + // ensure no current documents + verifyDeletedocsPass(ADMIN_USER, collectionName, true); // create documents ArrayList<SolrInputDocument> docs = new ArrayList<SolrInputDocument>(); @@ -134,106 +142,241 @@ public class TestDocLevelOperations extends AbstractSolrSentryTestBase { setAuthenticationUser("docLevel"); rsp = server.query(query); assertEquals(NUM_DOCS, rsp.getResults().getNumFound()); - - // test filter queries work as AND -- i.e. user can't avoid doc-level - // checks by prefixing their own filterQuery - setAuthenticationUser("junit"); - String fq = URLEncoder.encode(" {!raw f=" + AUTH_FIELD + " v=docLevel_role}"); - String path = "/" + collectionName + "/select?q=*:*&fq="+fq; - String retValue = makeHttpRequest(server, "GET", path, null, null); - assertTrue(retValue.contains("numFound=\"" + NUM_DOCS / 2 + "\" ")); - - // test that user can't inject an "OR" into the query - final String syntaxErrorMsg = "org.apache.solr.search.SyntaxError: Cannot parse"; - fq = URLEncoder.encode(" {!raw f=" + AUTH_FIELD + " v=docLevel_role} OR "); - path = "/" + collectionName + "/select?q=*:*&fq="+fq; - retValue = makeHttpRequest(server, "GET", path, null, null); - assertTrue(retValue.contains(syntaxErrorMsg)); - - // same test, prefix OR this time - fq = URLEncoder.encode(" OR {!raw f=" + AUTH_FIELD + " v=docLevel_role}"); - path = "/" + collectionName + "/select?q=*:*&fq="+fq; - retValue = makeHttpRequest(server, "GET", path, null, null); - assertTrue(retValue.contains(syntaxErrorMsg)); } finally { server.shutdown(); } } /** + * Test that queries from different users only return the documents they have access to. + */ + @Test + public void testDocLevelOperations() throws Exception { + String collectionName = "docLevelCollection"; + setupCollectionWithDocSecurity(collectionName); + + try { + createDocsAndQuerySimple(collectionName); + CloudSolrServer server = getCloudSolrServer(collectionName); + try { + // test filter queries work as AND -- i.e. user can't avoid doc-level + // checks by prefixing their own filterQuery + setAuthenticationUser("junit"); + String fq = URLEncoder.encode(" {!raw f=" + AUTH_FIELD + " v=docLevel_role}"); + String path = "/" + collectionName + "/select?q=*:*&fq="+fq; + String retValue = makeHttpRequest(server, "GET", path, null, null); + assertTrue(retValue.contains("numFound=\"" + NUM_DOCS / 2 + "\" ")); + + // test that user can't inject an "OR" into the query + final String syntaxErrorMsg = "org.apache.solr.search.SyntaxError: Cannot parse"; + fq = URLEncoder.encode(" {!raw f=" + AUTH_FIELD + " v=docLevel_role} OR "); + path = "/" + collectionName + "/select?q=*:*&fq="+fq; + retValue = makeHttpRequest(server, "GET", path, null, null); + assertTrue(retValue.contains(syntaxErrorMsg)); + + // same test, prefix OR this time + fq = URLEncoder.encode(" OR {!raw f=" + AUTH_FIELD + " v=docLevel_role}"); + path = "/" + collectionName + "/select?q=*:*&fq="+fq; + retValue = makeHttpRequest(server, "GET", path, null, null); + assertTrue(retValue.contains(syntaxErrorMsg)); + } finally { + server.shutdown(); + } + } finally { + deleteCollection(collectionName); + } + } + + /** * Test the allRolesToken. Make it a keyword in the query language ("OR") * to make sure it is treated literally rather than interpreted. */ @Test public void testAllRolesToken() throws Exception { - String allRolesToken = "OR"; String collectionName = "allRolesCollection"; setupCollectionWithDocSecurity(collectionName); - int junitFactor = 2; - int allRolesFactor = 5; - int totalJunitAdded = 0; // total docs added with junit token - int totalAllRolesAdded = 0; // total number of docs with the allRolesToken - int totalOnlyAllRolesAdded = 0; // total number of docs with _only_ the allRolesToken + try { + String allRolesToken = "OR"; + int junitFactor = 2; + int allRolesFactor = 5; - // create documents - ArrayList<SolrInputDocument> docs = new ArrayList<SolrInputDocument>(); - for (int i = 0; i < NUM_DOCS; ++i) { - boolean addedViaJunit = false; - SolrInputDocument doc = new SolrInputDocument(); - String iStr = Long.toString(i); - doc.addField("id", iStr); - doc.addField("description", "description" + iStr); + int totalJunitAdded = 0; // total docs added with junit token + int totalAllRolesAdded = 0; // total number of docs with the allRolesToken + int totalOnlyAllRolesAdded = 0; // total number of docs with _only_ the allRolesToken - if (i % junitFactor == 0) { - doc.addField(AUTH_FIELD, "junit_role"); - addedViaJunit = true; - ++totalJunitAdded; - } if (i % allRolesFactor == 0) { - doc.addField(AUTH_FIELD, allRolesToken); - ++totalAllRolesAdded; - if (!addedViaJunit) ++totalOnlyAllRolesAdded; + // create documents + ArrayList<SolrInputDocument> docs = new ArrayList<SolrInputDocument>(); + for (int i = 0; i < NUM_DOCS; ++i) { + boolean addedViaJunit = false; + SolrInputDocument doc = new SolrInputDocument(); + String iStr = Long.toString(i); + doc.addField("id", iStr); + doc.addField("description", "description" + iStr); + + if (i % junitFactor == 0) { + doc.addField(AUTH_FIELD, "junit_role"); + addedViaJunit = true; + ++totalJunitAdded; + } if (i % allRolesFactor == 0) { + doc.addField(AUTH_FIELD, allRolesToken); + ++totalAllRolesAdded; + if (!addedViaJunit) ++totalOnlyAllRolesAdded; + } + docs.add(doc); } - docs.add(doc); + // make sure our factors give us interesting results -- + // that some docs only have all roles and some only have junit + assert(totalOnlyAllRolesAdded > 0); + assert(totalJunitAdded > totalAllRolesAdded); + + CloudSolrServer server = getCloudSolrServer(collectionName); + try { + server.add(docs); + server.commit(true, true); + + // queries + SolrQuery query = new SolrQuery(); + query.setQuery("*:*"); + + // as admin -- should only get all roles token documents + setAuthenticationUser("admin"); + QueryResponse rsp = server.query(query); + SolrDocumentList docList = rsp.getResults(); + assertEquals(totalAllRolesAdded, docList.getNumFound()); + for (SolrDocument doc : docList) { + String id = doc.getFieldValue("id").toString(); + assertEquals(0, Long.valueOf(id) % allRolesFactor); + } + + // as junit -- should get junit added + onlyAllRolesAdded + setAuthenticationUser("junit"); + rsp = server.query(query); + docList = rsp.getResults(); + assertEquals(totalJunitAdded + totalOnlyAllRolesAdded, docList.getNumFound()); + for (SolrDocument doc : docList) { + String id = doc.getFieldValue("id").toString(); + boolean addedJunit = (Long.valueOf(id) % junitFactor) == 0; + boolean onlyAllRoles = !addedJunit && (Long.valueOf(id) % allRolesFactor) == 0; + assertEquals(true, addedJunit || onlyAllRoles); + } + } finally { + server.shutdown(); + } + } finally { + deleteCollection(collectionName); } - // make sure our factors give us interesting results -- - // that some docs only have all roles and some only have junit - assert(totalOnlyAllRolesAdded > 0); - assert(totalJunitAdded > totalAllRolesAdded); + } + /** + * delete the docs as "deleteUser" using deleteByQuery "deleteQueryStr". + * Verify that number of docs returned for "queryUser" equals + * "expectedQueryDocs" after deletion. + */ + private void deleteByQueryTest(String collectionName, String deleteUser, + String deleteByQueryStr, String queryUser, int expectedQueryDocs) throws Exception { + createDocsAndQuerySimple(collectionName); CloudSolrServer server = getCloudSolrServer(collectionName); try { - server.add(docs); - server.commit(true, true); + SolrQuery query = new SolrQuery(); + query.setQuery("*:*"); - // queries + setAuthenticationUser(deleteUser); + server.deleteByQuery(deleteByQueryStr); + server.commit(); + QueryResponse rsp = server.query(query); + long junitResults = rsp.getResults().getNumFound(); + assertEquals(0, junitResults); + + setAuthenticationUser(queryUser); + rsp = server.query(query); + long docLevelResults = rsp.getResults().getNumFound(); + assertEquals(expectedQueryDocs, docLevelResults); + } finally { + server.shutdown(); + } + } + + private void deleteByIdTest(String collectionName) throws Exception { + createDocsAndQuerySimple(collectionName); + CloudSolrServer server = getCloudSolrServer(collectionName); + try { SolrQuery query = new SolrQuery(); query.setQuery("*:*"); - // as admin -- should only get all roles token documents - setAuthenticationUser("admin"); - QueryResponse rsp = server.query(query); - SolrDocumentList docList = rsp.getResults(); - assertEquals(totalAllRolesAdded, docList.getNumFound()); - for (SolrDocument doc : docList) { - String id = doc.getFieldValue("id").toString(); - assertEquals(0, Long.valueOf(id) % allRolesFactor); + setAuthenticationUser("junit"); + List<String> allIds = new ArrayList<String>(NUM_DOCS); + for (int i = 0; i < NUM_DOCS; ++i) { + allIds.add(Long.toString(i)); } + server.deleteById(allIds); + server.commit(); + + QueryResponse rsp = server.query(query); + long junitResults = rsp.getResults().getNumFound(); + assertEquals(0, junitResults); + + setAuthenticationUser("docLevel"); + rsp = server.query(query); + long docLevelResults = rsp.getResults().getNumFound(); + assertEquals(0, docLevelResults); + } finally { + server.shutdown(); + } + } - // as junit -- should get junit added + onlyAllRolesAdded + private void updateDocsTest(String collectionName) throws Exception { + createDocsAndQuerySimple(collectionName); + CloudSolrServer server = getCloudSolrServer(collectionName); + try { setAuthenticationUser("junit"); + String docIdStr = Long.toString(1); + + // verify we can't view one of the odd documents + SolrQuery query = new SolrQuery(); + query.setQuery("id:"+docIdStr); + QueryResponse rsp = server.query(query); + assertEquals(0, rsp.getResults().getNumFound()); + + // overwrite the document that we can't see + ArrayList<SolrInputDocument> docs = new ArrayList<SolrInputDocument>(); + SolrInputDocument doc = new SolrInputDocument(); + doc.addField("id", docIdStr); + doc.addField("description", "description" + docIdStr); + doc.addField(AUTH_FIELD, "junit_role"); + docs.add(doc); + server.add(docs); + server.commit(); + + // verify we can now view the document rsp = server.query(query); - docList = rsp.getResults(); - assertEquals(totalJunitAdded + totalOnlyAllRolesAdded, docList.getNumFound()); - for (SolrDocument doc : docList) { - String id = doc.getFieldValue("id").toString(); - boolean addedJunit = (Long.valueOf(id) % junitFactor) == 0; - boolean onlyAllRoles = !addedJunit && (Long.valueOf(id) % allRolesFactor) == 0; - assertEquals(true, addedJunit || onlyAllRoles); - } + assertEquals(1, rsp.getResults().getNumFound()); } finally { server.shutdown(); } } + + @Test + public void testUpdateDeleteOperations() throws Exception { + String collectionName = "testUpdateDeleteOperations"; + + setupCollectionWithDocSecurity(collectionName); + try { + createDocsAndQuerySimple(collectionName); + + // test deleteByQuery "*:*" + deleteByQueryTest(collectionName, "junit", "*:*", "docLevel", 0); + + // test deleteByQuery non-*:* + deleteByQueryTest(collectionName, "junit", "sentry_auth:docLevel_role", "docLevel", 0); + + // test deleting all documents by Id + deleteByIdTest(collectionName); + + updateDocsTest(collectionName); + } finally { + deleteCollection(collectionName); + } + } } http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/7d214749/sentry-tests/sentry-tests-solr/src/test/resources/solr/sentry/test-authz-provider.ini ---------------------------------------------------------------------- diff --git a/sentry-tests/sentry-tests-solr/src/test/resources/solr/sentry/test-authz-provider.ini b/sentry-tests/sentry-tests-solr/src/test/resources/solr/sentry/test-authz-provider.ini index b7aa0c8..96ab0d4 100644 --- a/sentry-tests/sentry-tests-solr/src/test/resources/solr/sentry/test-authz-provider.ini +++ b/sentry-tests/sentry-tests-solr/src/test/resources/solr/sentry/test-authz-provider.ini @@ -29,9 +29,9 @@ admin_query_update_group = admin_query_update_role admin_all_group = admin_all_role [roles] -junit_role = collection=admin, collection=collection1, collection=docLevelCollection, collection=allRolesCollection -docLevel_role = collection=docLevelCollection -admin_role = collection=admin, collection=collection1, collection=sentryCollection, collection=sentryCollection_underlying1, collection=sentryCollection_underlying2, collection=docLevelCollection, collection=allRolesCollection, collection=testInvariantCollection +junit_role = collection=admin, collection=collection1, collection=docLevelCollection, collection=allRolesCollection, collection=testUpdateDeleteOperations +docLevel_role = collection=docLevelCollection, collection=testUpdateDeleteOperations +admin_role = collection=admin, collection=collection1, collection=sentryCollection, collection=sentryCollection_underlying1, collection=sentryCollection_underlying2, collection=docLevelCollection, collection=allRolesCollection, collection=testInvariantCollection, collection=testUpdateDeleteOperations sentryCollection_query_role = collection=sentryCollection->action=query sentryCollection_update_role = collection=sentryCollection->action=update sentryCollection_query_update_role = collection=sentryCollection->action=query, collection=sentryCollection->action=update
