Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.sentry.tests.e2e.solr; import static org.apache.sentry.tests.e2e.solr.TestSentryServer.ADMIN_USER; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.servlet.http.HttpServletResponse; import org.apache.sentry.core.common.exception.SentryUserException; import org.apache.sentry.core.model.solr.SolrConstants; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.impl.CloudSolrClient; import org.apache.solr.client.solrj.request.QueryRequest; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.SolrParams; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; /** * Test the document-level security features */ public class TestDocLevelOperations extends SolrSentryServiceTestBase { private static final String AUTH_FIELD = "sentry_auth"; private static final int NUM_DOCS = 100; private static final int EXTRA_AUTH_FIELDS = 2; @BeforeClass public static void setupPermissions() throws SentryUserException { sentryClient.createRole(ADMIN_USER, "junit_role", COMPONENT_SOLR); sentryClient.createRole(ADMIN_USER, "doclevel_role", COMPONENT_SOLR); sentryClient.grantRoleToGroups(ADMIN_USER, "junit_role", COMPONENT_SOLR, Collections.singleton("junit")); sentryClient.grantRoleToGroups(ADMIN_USER, "doclevel_role", COMPONENT_SOLR, Collections.singleton("doclevel")); // junit user grantAdminPrivileges(ADMIN_USER, "junit_role", SolrConstants.ALL, SolrConstants.ALL); grantCollectionPrivileges(ADMIN_USER, "junit_role", "docLevelCollection", SolrConstants.ALL); grantCollectionPrivileges(ADMIN_USER, "junit_role", "allRolesCollection", SolrConstants.ALL); grantCollectionPrivileges(ADMIN_USER, "junit_role", "testUpdateDeleteOperations", SolrConstants.ALL); // docLevel user grantCollectionPrivileges(ADMIN_USER, "doclevel_role", "docLevelCollection", SolrConstants.ALL); grantCollectionPrivileges(ADMIN_USER, "doclevel_role", "testUpdateDeleteOperations", SolrConstants.ALL); // admin user grantCollectionPrivileges(ADMIN_USER, ADMIN_ROLE, SolrConstants.ALL, SolrConstants.ALL); } @Before public void resetAuthenticatedUser() { setAuthenticationUser("admin"); } private QueryRequest getRealTimeGetRequest() { // real time get request StringBuilder idsBuilder = new StringBuilder("0"); for (int i = 1; i < NUM_DOCS; ++i) { idsBuilder.append("," + i); } return getRealTimeGetRequest(idsBuilder.toString()); } @SuppressWarnings("serial") private QueryRequest getRealTimeGetRequest(String ids) { final ModifiableSolrParams idsParams = new ModifiableSolrParams(); idsParams.add("ids", ids); return new QueryRequest() { @Override public String getPath() { return "/get"; } @Override public SolrParams getParams() { return idsParams; } }; } /** * 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 */ private void createDocsAndQuerySimple(String collectionName, boolean checkNonAdminUsers) throws Exception { // ensure no current documents verifyDeletedocsPass(ADMIN_USER, collectionName, true); DocLevelGenerator generator = new DocLevelGenerator(collectionName, AUTH_FIELD); generator.generateDocs(cluster.getSolrClient(), NUM_DOCS, "junit_role", "admin_role", EXTRA_AUTH_FIELDS); querySimple(collectionName, new QueryRequest(new SolrQuery("*:*")), cluster.getSolrClient(), checkNonAdminUsers); querySimple(collectionName, getRealTimeGetRequest(), cluster.getSolrClient(), checkNonAdminUsers); } private void querySimple(String collectionName, QueryRequest request, CloudSolrClient client, boolean checkNonAdminUsers) throws Exception { // as admin -- should get the other half setAuthenticationUser("admin"); QueryResponse rsp = request.process(client, collectionName); SolrDocumentList docList = rsp.getResults(); assertEquals(NUM_DOCS / 2, docList.getNumFound()); for (SolrDocument doc : docList) { String id = doc.getFieldValue("id").toString(); assertEquals(1, Long.valueOf(id) % 2); } if (checkNonAdminUsers) { // as junit -- should get half the documents setAuthenticationUser("junit"); rsp = request.process(client, collectionName); docList = rsp.getResults(); assertEquals(NUM_DOCS / 2, docList.getNumFound()); for (SolrDocument doc : docList) { String id = doc.getFieldValue("id").toString(); assertEquals(0, Long.valueOf(id) % 2); } // as docLevel -- should get all setAuthenticationUser("doclevel"); rsp = request.process(client, collectionName); assertEquals(NUM_DOCS, rsp.getResults().getNumFound()); } } /** * Test that queries from different users only return the documents they have access to. */ @Test public void testDocLevelOperations() throws Exception { String collectionName = "docLevelCollection"; createCollection(ADMIN_USER, collectionName, "cloud-minimal_doc_level_security", NUM_SERVERS, 1); CloudSolrClient client = cluster.getSolrClient(); createDocsAndQuerySimple(collectionName, true); // 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(client, "GET", path, null, null, HttpServletResponse.SC_OK); assertTrue("Result : " + retValue, 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(client, "GET", path, null, null, HttpServletResponse.SC_BAD_REQUEST); 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(client, "GET", path, null, null, HttpServletResponse.SC_BAD_REQUEST); assertTrue(retValue.contains(syntaxErrorMsg)); } /** * 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 collectionName = "allRolesCollection"; createCollection(ADMIN_USER, collectionName, "cloud-minimal_doc_level_security", NUM_SERVERS, 1); String allRolesToken = "OR"; 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 // 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); } // 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); cluster.getSolrClient().add(collectionName, docs); cluster.getSolrClient().commit(collectionName, true, true); checkAllRolesToken(collectionName, new QueryRequest(new SolrQuery("*:*")), cluster.getSolrClient(), totalAllRolesAdded, totalOnlyAllRolesAdded, allRolesFactor, totalJunitAdded, junitFactor); checkAllRolesToken(collectionName, getRealTimeGetRequest(), cluster.getSolrClient(), totalAllRolesAdded, totalOnlyAllRolesAdded, allRolesFactor, totalJunitAdded, junitFactor); } private void checkAllRolesToken(String collectionName, QueryRequest request, CloudSolrClient client, int totalAllRolesAdded, int totalOnlyAllRolesAdded, int allRolesFactor, int totalJunitAdded, int junitFactor) throws Exception { // as admin -- should only get all roles token documents setAuthenticationUser("admin"); QueryResponse rsp = request.process(client, collectionName); 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 = request.process(client, collectionName); 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); } } /** * 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, true); setAuthenticationUser(deleteUser); cluster.getSolrClient().deleteByQuery(collectionName, deleteByQueryStr); cluster.getSolrClient().commit(collectionName); checkDeleteByQuery(collectionName, new QueryRequest(new SolrQuery("*:*")), cluster.getSolrClient(), queryUser, expectedQueryDocs); checkDeleteByQuery(collectionName, getRealTimeGetRequest(), cluster.getSolrClient(), queryUser, expectedQueryDocs); } private void checkDeleteByQuery(String collectionName, QueryRequest query, CloudSolrClient server, String queryUser, int expectedQueryDocs) throws Exception { QueryResponse rsp = query.process(server, collectionName); long junitResults = rsp.getResults().getNumFound(); assertEquals(0, junitResults); setAuthenticationUser(queryUser); rsp = query.process(server, collectionName); long docLevelResults = rsp.getResults().getNumFound(); assertEquals(expectedQueryDocs, docLevelResults); } private void deleteByIdTest(String collectionName) throws Exception { createDocsAndQuerySimple(collectionName, true); setAuthenticationUser("junit"); List<String> allIds = new ArrayList<String>(NUM_DOCS); for (int i = 0; i < NUM_DOCS; ++i) { allIds.add(Long.toString(i)); } cluster.getSolrClient().deleteById(collectionName, allIds); cluster.getSolrClient().commit(collectionName); checkDeleteById(collectionName, new QueryRequest(new SolrQuery("*:*")), cluster.getSolrClient()); checkDeleteById(collectionName, getRealTimeGetRequest(), cluster.getSolrClient()); } private void checkDeleteById(String collectionName, QueryRequest request, CloudSolrClient server) throws Exception { QueryResponse rsp = request.process(server, collectionName); long junitResults = rsp.getResults().getNumFound(); assertEquals(0, junitResults); setAuthenticationUser("doclevel"); rsp = request.process(server, collectionName); long docLevelResults = rsp.getResults().getNumFound(); assertEquals(0, docLevelResults); } private void updateDocsTest(String collectionName) throws Exception { createDocsAndQuerySimple(collectionName, true); setAuthenticationUser("junit"); String docIdStr = Long.toString(1); // verify we can't view one of the odd documents QueryRequest query = new QueryRequest(new SolrQuery("id:" + docIdStr)); QueryRequest rtgQuery = getRealTimeGetRequest(docIdStr); checkUpdateDocsQuery(collectionName, query, cluster.getSolrClient(), 0); checkUpdateDocsQuery(collectionName, rtgQuery, cluster.getSolrClient(), 0); // 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); cluster.getSolrClient().add(collectionName, docs); cluster.getSolrClient().commit(collectionName); // verify we can now view the document checkUpdateDocsQuery(collectionName, query, cluster.getSolrClient(), 1); checkUpdateDocsQuery(collectionName, rtgQuery, cluster.getSolrClient(), 1); } private void checkUpdateDocsQuery(String collectionName, QueryRequest request, CloudSolrClient server, int expectedDocs) throws Exception { QueryResponse rsp = request.process(server, collectionName); assertEquals(expectedDocs, rsp.getResults().getNumFound()); } @Test public void testUpdateDeleteOperations() throws Exception { String collectionName = "testUpdateDeleteOperations"; createCollection(ADMIN_USER, collectionName, "cloud-minimal_doc_level_security", NUM_SERVERS, 1); createDocsAndQuerySimple(collectionName, true); // test deleteByQuery "*:*" - we expect this to delete all docs, and it does. deleteByQueryTest(collectionName, "junit", "*:*", "doclevel", 0); // test deleteByQuery non-*:* - this proves that the junit can delete documents (sentry_auth:doclevel_role) that he can't see. // We verify this with querying as doclevel (who can see all, and he now sees zero of these docs) deleteByQueryTest(collectionName, "junit", "sentry_auth:doclevel_role", "doclevel", 0); // test deleting all documents by Id - in this case the junit user can delete all docs deleteByIdTest(collectionName); // This is testing the expected behaviour that if we have been granted the ALL role then we can // update docs that we can't see, even to the point that we make them visible. updateDocsTest(collectionName); } /** * Test to validate doc level security on collections without perm for Index level auth. * @throws Exception */ @Test public void indexDocAuthTests() throws Exception { String collectionName = "testIndexlevelDoclevelOperations"; createCollection(ADMIN_USER, collectionName, "cloud-minimal_doc_level_security", NUM_SERVERS, 1); createDocsAndQuerySimple(collectionName, false); // test query for "*:*" fails as junit user (junit user doesn't have index level permissions but has doc level permissions set) verifyQueryFail("junit", collectionName, ALL_DOCS); // test query for "*:*" fails as docLevel user (docLevel user has neither index level permissions nor doc level permissions set) verifyQueryFail("doclevel", collectionName, ALL_DOCS); } }