package com.constellio.app.api.search;
import static com.constellio.model.entities.schemas.Schemas.TITLE;
import static com.constellio.model.entities.security.global.AuthorizationAddRequest.authorizationForUsers;
import static com.constellio.sdk.tests.schemas.TestsSchemasSetup.whichIsMultivalue;
import static java.util.Arrays.asList;
import static junit.framework.Assert.fail;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.junit.Before;
import org.junit.Test;
import com.constellio.model.entities.records.wrappers.User;
import com.constellio.model.entities.schemas.Schemas;
import com.constellio.model.entities.security.Authorization;
import com.constellio.model.entities.security.global.AuthorizationDetails;
import com.constellio.model.entities.security.CustomizedAuthorizationsBehavior;
import com.constellio.model.entities.security.Role;
import com.constellio.model.entities.security.global.AuthorizationAddRequest;
import com.constellio.model.entities.security.global.UserCredential;
import com.constellio.model.services.records.RecordServices;
import com.constellio.model.services.records.RecordServicesException;
import com.constellio.model.services.schemas.ModificationImpactCalculatorAcceptSetup.AnotherSchemaMetadatas;
import com.constellio.model.services.schemas.builders.MetadataSchemaTypesBuilder;
import com.constellio.model.services.search.FreeTextSearchServices;
import com.constellio.model.services.search.query.logical.FreeTextQuery;
import com.constellio.model.services.security.AuthorizationsServices;
import com.constellio.model.services.security.authentification.AuthenticationService;
import com.constellio.model.services.users.UserServices;
import com.constellio.sdk.tests.ConstellioTest;
import com.constellio.sdk.tests.TestRecord;
import com.constellio.sdk.tests.annotations.SlowTest;
import com.constellio.sdk.tests.schemas.MetadataSchemaTypesConfigurator;
import com.constellio.sdk.tests.schemas.TestsSchemasSetup;
import com.constellio.sdk.tests.schemas.TestsSchemasSetup.ZeSchemaMetadatas;
import com.constellio.sdk.tests.setups.Users;
import javax.servlet.http.HttpServletResponse;
@SlowTest
public class FreeTextSearchSecurityAcceptTest extends ConstellioTest {
String anotherCollection = "otherCollection";
TestsSchemasSetup anotherCollectionSetup = new TestsSchemasSetup(anotherCollection);
ZeSchemaMetadatas anotherCollectionSchema = anotherCollectionSetup.new ZeSchemaMetadatas();
TestsSchemasSetup zeCollectionSetup = new TestsSchemasSetup(zeCollection);
ZeSchemaMetadatas zeCollectionSchema = zeCollectionSetup.new ZeSchemaMetadatas();
TestsSchemasSetup.AnotherSchemaMetadatas zeCollectionUnsecuredSchema = zeCollectionSetup.new AnotherSchemaMetadatas();
RecordServices recordServices;
UserServices userServices;
UserCredential userWithZeCollectionReadAccess;
UserCredential userWithAnotherCollectionReadAccess;
UserCredential userWithBothCollectionReadAccess;
UserCredential userInBothCollectionWithoutAnyAccess;
UserCredential userInNoCollection;
UserCredential userWithSomeRecordAccess;
UserCredential systemAdmin;
String zeCollectionRecord1 = "zeCollectionRecord1";
String zeCollectionRecord2 = "zeCollectionRecord2";
String zeCollectionNonSecuredRecord1 = "zeCollectionNonSecuredRecord1";
String zeCollectionNonSecuredRecord2 = "zeCollectionNonSecuredRecord2";
String anotherCollectionRecord1 = "anotherCollectionRecord1";
String anotherCollectionRecord2 = "anotherCollectionRecord2";
Users users = new Users();
@Before
public void setUp()
throws Exception {
prepareSystem(
withZeCollection().withAllTest(users),
withCollection(anotherCollection)
);
recordServices = getModelLayerFactory().newRecordServices();
userServices = getModelLayerFactory().newUserServices();
// givenCollection(zeCollection, asList("fr", "en"));
// givenCollection(anotherCollection, asList("fr"));
defineSchemasManager().using(zeCollectionSetup.withSecurityFlag(true)
.withAStringMetadata(whichIsMultivalue).withAContentMetadata().with(new MetadataSchemaTypesConfigurator() {
@Override
public void configure(MetadataSchemaTypesBuilder schemaTypes) {
schemaTypes.getSchemaType("anotherSchemaType").setSecurity(false);
}
}));
defineSchemasManager().using(anotherCollectionSetup.withSecurityFlag(true)
.withAStringMetadata(whichIsMultivalue).withAContentListMetadata());
setupUsers();
assertThatUserIsInCollection(users.alice().getUsername(), "zeCollection");
setupUserPermissions();
setupRecords();
setupAuthorizations();
}
@Test
public void givenUserWithCollectionReadAccessWhenSearchingWithFreeTextUsingWildcardThenSeeAllRecordsOfTheCollection()
throws Exception {
assertThat(findAllRecordsVisibleBy(userWithZeCollectionReadAccess))
.containsOnly(zeCollectionRecord1, zeCollectionRecord2, zeCollectionNonSecuredRecord1,
zeCollectionNonSecuredRecord2);
assertThat(findAllRecordsVisibleBy(userWithAnotherCollectionReadAccess))
.containsOnly(anotherCollectionRecord1, anotherCollectionRecord2);
}
@Test
public void givenUserInBothCollectionWithReadAccessWhenSearchingWithFreeTextUsingWildcardThenSeeAllRecordsOfBothCollection()
throws Exception {
assertThat(findAllRecordsVisibleBy(userWithBothCollectionReadAccess))
.containsOnly(zeCollectionRecord1, zeCollectionRecord2, anotherCollectionRecord1, anotherCollectionRecord2,
zeCollectionNonSecuredRecord1, zeCollectionNonSecuredRecord2);
}
@Test
public void givenUserInBothCollectionWithoutReadAccessWhenSearchingWithFreeTextUsingWildcardThenSeeNothing()
throws Exception {
assertThat(findAllRecordsVisibleBy(userInBothCollectionWithoutAnyAccess)).isEmpty();
}
@Test
public void givenUserInNoCollectionWhenSearchingWithFreeTextUsingWildcardThenSeeNothing()
throws Exception {
assertThat(findAllRecordsVisibleBy(userInNoCollection)).isEmpty();
}
@Test
public void givenUserWithSomeRecordAccessWhenSearchingWithFreeTextUsingWildcardThenSeeOnlyAccessibleRecords()
throws Exception {
assertThat(findAllRecordsVisibleBy(userWithSomeRecordAccess)).containsOnly(zeCollectionRecord1, anotherCollectionRecord1);
}
@Test
public void whenCheckingIfSecurityEnabledThenBasedOnCollectionAndSchema()
throws Exception {
FreeTextSearchServices searchServices = new FreeTextSearchServices(getModelLayerFactory());
ModifiableSolrParams params = new ModifiableSolrParams();
params.add("q", "*:*");
params.add("fq", "collection_s:zeCollection");
params.add("fq", "schema_s:anotherSchemaType");
assertThat(searchServices.isSecurityEnabled(params)).isFalse();
params = new ModifiableSolrParams();
params.add("q", "*:*");
params.add("fq", "schema_s:anotherSchemaType");
assertThat(searchServices.isSecurityEnabled(params)).isTrue();
params = new ModifiableSolrParams();
params.add("q", "*:*");
params.add("fq", "collection_s:zeCollection");
assertThat(searchServices.isSecurityEnabled(params)).isTrue();
params = new ModifiableSolrParams();
params.add("q", "*:*");
params.add("fq", "collection_s:zeCollection");
params.add("fq", "schema_s:zeSchemaType");
assertThat(searchServices.isSecurityEnabled(params)).isTrue();
params = new ModifiableSolrParams();
params.add("q", "collection_s:zeCollection");
params.add("fq", "schema_s:anotherSchemaType");
assertThat(searchServices.isSecurityEnabled(params)).isFalse();
params = new ModifiableSolrParams();
params.add("q", "collection_s:zeCollection");
params.add("fq", "schema_s:zeSchemaType");
assertThat(searchServices.isSecurityEnabled(params)).isTrue();
params = new ModifiableSolrParams();
params.add("fq", "collection_s:zeCollection");
params.add("q", "schema_s:anotherSchemaType");
assertThat(searchServices.isSecurityEnabled(params)).isFalse();
params = new ModifiableSolrParams();
params.add("fq", "collection_s:zeCollection");
params.add("q", "schema_s:zeSchemaType");
assertThat(searchServices.isSecurityEnabled(params)).isTrue();
}
@Test
public void givenUserWithSomeAccessWhenSearchingUsingWebServiceWithOnNonSecuredSchemaThenSeeAllResults()
throws SolrServerException, IOException {
assertThatUserIsInCollection(users.alice().getUsername(), "zeCollection");
assertThat(findAllRecordsVisibleByUsingWebService(userWithZeCollectionReadAccess))
.containsOnly(zeCollectionRecord1, zeCollectionRecord2);
assertThat(findAllRecordsVisibleByUsingWebService(userWithAnotherCollectionReadAccess))
.containsOnly(anotherCollectionRecord1, anotherCollectionRecord2);
assertThat(findAllRecordsVisibleByUsingWebService(userWithBothCollectionReadAccess))
.containsOnly(zeCollectionRecord1, zeCollectionRecord2, anotherCollectionRecord1, anotherCollectionRecord2);
assertThat(findAllRecordsVisibleByUsingWebService(userInBothCollectionWithoutAnyAccess)).isEmpty();
assertThat(findAllRecordsVisibleByUsingWebService(userInNoCollection)).isEmpty();
assertThat(findAllRecordsVisibleByUsingWebService(userWithSomeRecordAccess))
.containsOnly(zeCollectionRecord1, anotherCollectionRecord1);
assertThat(findAllRecordsVisibleOfNonSecuredSchemaByUsingWebService(userWithSomeRecordAccess))
.containsOnly(zeCollectionNonSecuredRecord1, zeCollectionNonSecuredRecord2);
whenSearchingWithAvalidServiceKeyFromAnotherUserThenException();
whenSearchingWithInvalidTokenThenException();
whenSearchingWithNoTokenThenException();
whenSearchingWithNoServiceKeyThenException();
}
@Test
public void whenSearchingUsingWebServiceThenSameResults()
throws SolrServerException, IOException {
assertThatUserIsInCollection(users.alice().getUsername(), "zeCollection");
assertThat(findAllRecordsVisibleByUsingWebService(userWithZeCollectionReadAccess))
.containsOnly(zeCollectionRecord1, zeCollectionRecord2);
assertThat(findAllRecordsVisibleByUsingWebService(userWithAnotherCollectionReadAccess))
.containsOnly(anotherCollectionRecord1, anotherCollectionRecord2);
assertThat(findAllRecordsVisibleByUsingWebService(userWithBothCollectionReadAccess))
.containsOnly(zeCollectionRecord1, zeCollectionRecord2, anotherCollectionRecord1, anotherCollectionRecord2);
assertThat(findAllRecordsVisibleByUsingWebService(userInBothCollectionWithoutAnyAccess)).isEmpty();
assertThat(findAllRecordsVisibleByUsingWebService(userInNoCollection)).isEmpty();
assertThat(findAllRecordsVisibleByUsingWebService(userWithSomeRecordAccess))
.containsOnly(zeCollectionRecord1, anotherCollectionRecord1);
whenSearchingWithAvalidServiceKeyFromAnotherUserThenException();
whenSearchingWithInvalidTokenThenException();
whenSearchingWithNoTokenThenException();
whenSearchingWithNoServiceKeyThenException();
}
@Test
public void whenSearchingThenDoNotFindEvents()
throws Exception {
SolrClient solrServer = newSearchClient();
ModifiableSolrParams solrParams = new ModifiableSolrParams().set("q", "eventType_s:RECORD_UPDATE");
String serviceKey = userServices.giveNewServiceToken(userServices.getUserCredential(systemAdmin.getUsername()));
String token = userServices.getToken(serviceKey, systemAdmin.getUsername(), "youshallnotpass");
solrParams.set("serviceKey", serviceKey);
solrParams.set("token", token);
assertThat(solrServer.query(solrParams).getResults()).isEmpty();
solrParams = new ModifiableSolrParams().set("q", "type_s:RECORD_UPDATE");
solrParams.set("serviceKey", serviceKey);
solrParams.set("token", token);
solrParams.set("searchEvents", "false");
assertThat(solrServer.query(solrParams).getResults()).isEmpty();
}
private void setupRecords()
throws IOException, RecordServicesException {
User zeCollectionUser = userServices.getUserInCollection(userWithBothCollectionReadAccess.getUsername(), zeCollection);
User anotherCollectionUser = userServices.getUserInCollection(userWithBothCollectionReadAccess.getUsername(),
anotherCollection);
String title = "Au secours, je suis perdu";
recordServices.add(new TestRecord(zeCollectionSchema, zeCollectionRecord1).set(TITLE, title), zeCollectionUser);
recordServices.add(new TestRecord(zeCollectionSchema, zeCollectionRecord2).set(TITLE, title), zeCollectionUser);
recordServices.add(new TestRecord(zeCollectionUnsecuredSchema, zeCollectionNonSecuredRecord1).set(TITLE, title));
recordServices.add(new TestRecord(zeCollectionUnsecuredSchema, zeCollectionNonSecuredRecord2).set(TITLE, title));
recordServices.add(new TestRecord(anotherCollectionSchema, anotherCollectionRecord1).set(TITLE, title),
anotherCollectionUser);
recordServices.add(new TestRecord(anotherCollectionSchema, anotherCollectionRecord2).set(TITLE, title),
anotherCollectionUser);
}
private List<String> findAllRecordsVisibleBy(UserCredential user) {
FreeTextSearchServices freeTextSearchServices = getModelLayerFactory().newFreeTextSearchServices();
SolrParams solrParams = new ModifiableSolrParams().set("q", "search_txt_fr:perdu");
UserServices userServices = getModelLayerFactory().newUserServices();
UserCredential refreshedUser = userServices.getUser(user.getUsername());
List<String> records = new ArrayList<>();
QueryResponse response = freeTextSearchServices.search(new FreeTextQuery(solrParams).filteredByUser(refreshedUser));
for (SolrDocument result : response.getResults()) {
records.add((String) result.getFieldValue("id"));
}
return records;
}
private void whenSearchingWithInvalidTokenThenException()
throws SolrServerException, IOException {
SolrClient solrServer = newSearchClient();
ModifiableSolrParams solrParams = new ModifiableSolrParams().set("q", "search_txt_fr:perdu");
String serviceKey = userServices
.giveNewServiceToken(userServices.getUserCredential(userWithBothCollectionReadAccess.getUsername()));
solrParams.set("serviceKey", serviceKey);
solrParams.set("token", "noidea");
try {
solrServer.query(solrParams);
fail("Exception expected");
} catch (RuntimeException e) {
assertThat(e.getMessage())
.contains("Invalid serviceKey/token");
}
}
private void whenSearchingWithAvalidServiceKeyFromAnotherUserThenException()
throws SolrServerException, IOException {
SolrClient solrServer = newSearchClient();
ModifiableSolrParams solrParams = new ModifiableSolrParams().set("q", "search_txt_fr:perdu");
String serviceKey = userServices
.giveNewServiceToken(userServices.getUserCredential(userWithBothCollectionReadAccess.getUsername()));
String anotherUserToken = userServices.generateToken(userWithZeCollectionReadAccess.getUsername());
solrParams.set("serviceKey", serviceKey);
solrParams.set("token", anotherUserToken);
try {
assertThat(solrServer.query(solrParams).getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
fail("Exception expected");
} catch (RuntimeException e) {
assertThat(e.getMessage())
.contains("Invalid serviceKey/token");
}
}
private void whenSearchingWithNoTokenThenException()
throws SolrServerException, IOException {
SolrClient solrServer = newSearchClient();
ModifiableSolrParams solrParams = new ModifiableSolrParams().set("q", "search_txt_fr:perdu");
String serviceKey = userServices
.giveNewServiceToken(userServices.getUserCredential(userWithBothCollectionReadAccess.getUsername()));
solrParams.set("serviceKey", serviceKey);
try {
solrServer.query(solrParams);
fail("Exception expected");
} catch (RuntimeException e) {
assertThat(e.getMessage())
.contains("Invalid serviceKey/token");
}
}
private void whenSearchingWithNoServiceKeyThenException()
throws SolrServerException, IOException {
SolrClient solrServer = newSearchClient();
ModifiableSolrParams solrParams = new ModifiableSolrParams().set("q", "search_txt_fr:perdu");
String token = userServices.generateToken(userWithBothCollectionReadAccess.getUsername());
solrParams.set("token", token);
try {
solrServer.query(solrParams);
fail("Exception expected");
} catch (RuntimeException e) {
assertThat(e.getMessage())
.contains("Invalid serviceKey/token");
}
}
private List<String> findAllRecordsVisibleByUsingWebService(UserCredential user)
throws SolrServerException, IOException {
SolrClient solrServer = newSearchClient();
ModifiableSolrParams solrParams = new ModifiableSolrParams().set("q", "schema_s:zeSchemaType*");
String serviceKey = userServices.giveNewServiceToken(userServices.getUserCredential(user.getUsername()));
String token = userServices.getToken(serviceKey, user.getUsername(), "youshallnotpass");
solrParams.set("serviceKey", serviceKey);
solrParams.set("token", token);
List<String> records = new ArrayList<>();
QueryResponse response = solrServer.query(solrParams);
for (SolrDocument result : response.getResults()) {
records.add((String) result.getFieldValue("id"));
}
return records;
}
private List<String> findAllRecordsVisibleOfNonSecuredSchemaByUsingWebService(UserCredential user)
throws SolrServerException, IOException {
SolrClient solrServer = newSearchClient();
ModifiableSolrParams solrParams = new ModifiableSolrParams().set("q", "schema_s:anotherSchemaType*");
solrParams.add("fq", "collection_s:zeCollection");
String serviceKey = userServices.giveNewServiceToken(userServices.getUserCredential(user.getUsername()));
String token = userServices.getToken(serviceKey, user.getUsername(), "youshallnotpass");
solrParams.set("serviceKey", serviceKey);
solrParams.set("token", token);
List<String> records = new ArrayList<>();
QueryResponse response = solrServer.query(solrParams);
for (SolrDocument result : response.getResults()) {
records.add((String) result.getFieldValue("id"));
}
return records;
}
private void setupUsers()
throws RecordServicesException, InterruptedException {
UserServices userServices = getModelLayerFactory().newUserServices();
userServices.addUpdateUserCredential(users.chuckNorris().withSystemAdminPermission());
userWithZeCollectionReadAccess = users.alice();
userWithAnotherCollectionReadAccess = users.bob();
userWithBothCollectionReadAccess = users.charles();
userInBothCollectionWithoutAnyAccess = users.dakotaLIndien();
userInNoCollection = users.edouardLechat();
userWithSomeRecordAccess = users.gandalfLeblanc();
systemAdmin = users.chuckNorris();
userServices.addUserToCollection(userWithZeCollectionReadAccess, zeCollection);
userServices.addUserToCollection(userWithAnotherCollectionReadAccess, anotherCollection);
userServices.addUserToCollection(userWithBothCollectionReadAccess, zeCollection);
userServices.addUserToCollection(userWithBothCollectionReadAccess, anotherCollection);
userServices.addUserToCollection(userInBothCollectionWithoutAnyAccess, zeCollection);
userServices.addUserToCollection(userInBothCollectionWithoutAnyAccess, anotherCollection);
userServices.addUserToCollection(userWithSomeRecordAccess, zeCollection);
userServices.addUserToCollection(userWithSomeRecordAccess, anotherCollection);
AuthenticationService authenticationService = getModelLayerFactory().newAuthenticationService();
authenticationService.changePassword(systemAdmin.getUsername(), "youshallnotpass");
authenticationService.changePassword(userWithZeCollectionReadAccess.getUsername(), "youshallnotpass");
authenticationService.changePassword(userWithAnotherCollectionReadAccess.getUsername(), "youshallnotpass");
authenticationService.changePassword(userWithBothCollectionReadAccess.getUsername(), "youshallnotpass");
authenticationService.changePassword(userInBothCollectionWithoutAnyAccess.getUsername(), "youshallnotpass");
authenticationService.changePassword(userInNoCollection.getUsername(), "youshallnotpass");
authenticationService.changePassword(userWithSomeRecordAccess.getUsername(), "youshallnotpass");
waitForBatchProcess();
userWithZeCollectionReadAccess = userServices.getUserCredential(userWithZeCollectionReadAccess.getUsername());
userWithAnotherCollectionReadAccess = userServices.getUserCredential(userWithAnotherCollectionReadAccess.getUsername());
userWithBothCollectionReadAccess = userServices.getUserCredential(userWithBothCollectionReadAccess.getUsername());
userInBothCollectionWithoutAnyAccess = userServices.getUserCredential(userInBothCollectionWithoutAnyAccess.getUsername());
userInNoCollection = userServices.getUserCredential(userInNoCollection.getUsername());
userWithSomeRecordAccess = userServices.getUserCredential(userWithSomeRecordAccess.getUsername());
systemAdmin = userServices.getUserCredential(systemAdmin.getUsername());
}
private void assertThatUserIsInCollection(String username, String collection) {
UserCredential userCredential = userServices.getUser(username);
assertThatUserIsInCollection(userCredential, collection);
}
private void assertThatUserIsInCollection(UserCredential userCredential, String collection) {
assertThat(userCredential.getCollections()).contains(collection);
}
private void setupUserPermissions()
throws RecordServicesException {
recordServices.update(userServices.getUserInCollection(userWithZeCollectionReadAccess.getUsername(),
zeCollection).setCollectionReadAccess(true));
recordServices.update(userServices.getUserInCollection(userWithAnotherCollectionReadAccess.getUsername(),
anotherCollection)
.setCollectionReadAccess(true));
recordServices
.update(userServices.getUserInCollection(userWithBothCollectionReadAccess.getUsername(),
zeCollection).setCollectionWriteAccess(true));
recordServices.update(userServices.getUserInCollection(userWithBothCollectionReadAccess.getUsername(), anotherCollection)
.setCollectionWriteAccess(true));
}
private void setupAuthorizations()
throws RecordServicesException, InterruptedException {
AuthorizationsServices authorizationsServices = getModelLayerFactory().newAuthorizationsServices();
authorizationsServices.add(authorizationForUsers(users.gandalfLeblancIn(zeCollection))
.on(zeCollectionRecord1).givingReadAccess());
authorizationsServices.add(authorizationForUsers(users.gandalfLeblancIn(anotherCollection))
.on(anotherCollectionRecord1).givingReadAccess());
try {
waitForBatchProcess();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}