package com.constellio.model.services.users;
import static com.constellio.data.dao.dto.records.OptimisticLockingResolution.EXCEPTION;
import static com.constellio.data.utils.LangUtils.valueOrDefault;
import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.from;
import static com.constellio.model.services.users.UserUtils.cleanUsername;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.joda.time.LocalDateTime;
import com.constellio.data.dao.dto.records.OptimisticLockingResolution;
import com.constellio.data.threads.BackgroundThreadConfiguration;
import com.constellio.data.threads.BackgroundThreadExceptionHandling;
import com.constellio.data.threads.BackgroundThreadsManager;
import com.constellio.data.utils.TimeProvider;
import com.constellio.model.conf.ModelLayerConfiguration;
import com.constellio.model.entities.records.ActionExecutorInBatch;
import com.constellio.model.entities.records.Record;
import com.constellio.model.entities.records.Transaction;
import com.constellio.model.entities.records.wrappers.Collection;
import com.constellio.model.entities.security.global.SolrUserCredential;
import com.constellio.model.entities.security.global.UserCredential;
import com.constellio.model.entities.security.global.UserCredentialStatus;
import com.constellio.model.services.factories.ModelLayerFactory;
import com.constellio.model.services.factories.SystemCollectionListener;
import com.constellio.model.services.records.RecordServicesException;
import com.constellio.model.services.records.SchemasRecordsServices;
import com.constellio.model.services.search.SearchServices;
import com.constellio.model.services.search.query.logical.LogicalSearchQuery;
import com.constellio.model.services.users.UserCredentialsManagerRuntimeException.UserCredentialsManagerRuntimeException_CannotExecuteTransaction;
public class SolrUserCredentialsManager implements UserCredentialsManager, SystemCollectionListener {
private final ModelLayerFactory modelLayerFactory;
private final SearchServices searchServices;
private final SchemasRecordsServices schemas;
public SolrUserCredentialsManager(ModelLayerFactory modelLayerFactory) {
this.modelLayerFactory = modelLayerFactory;
modelLayerFactory.addSystemCollectionListener(this);
searchServices = modelLayerFactory.newSearchServices();
schemas = new SchemasRecordsServices(Collection.SYSTEM_COLLECTION, modelLayerFactory);
}
@Override
public UserCredential create(String username, String firstName, String lastName, String email, String serviceKey,
boolean systemAdmin, List<String> globalGroups, List<String> collections, Map<String, LocalDateTime> tokens,
UserCredentialStatus status, String domain, List<String> msExchDelegateListBL, String dn) {
return ((SolrUserCredential) valueOrDefault(getUserCredential(username), schemas.newCredential()))
.setUsername(cleanUsername(username))
.setFirstName(firstName)
.setLastName(lastName)
.setEmail(email)
.setServiceKey(serviceKey)
.setSystemAdmin(systemAdmin)
.setGlobalGroups(globalGroups)
.setCollections(collections)
.setAccessTokens(tokens)
.setStatus(status)
.setDomain(domain)
.setMsExchDelegateListBL(msExchDelegateListBL)
.setDn(dn);
}
@Override
public UserCredential create(String username, String firstName, String lastName, String email, List<String> personalEmails,
String serviceKey,
boolean systemAdmin, List<String> globalGroups, List<String> collections, Map<String, LocalDateTime> tokens,
UserCredentialStatus status, String domain, List<String> msExchDelegateListBL, String dn) {
return ((SolrUserCredential) valueOrDefault(getUserCredential(username), schemas.newCredential()))
.setUsername(cleanUsername(username))
.setFirstName(firstName)
.setLastName(lastName)
.setEmail(email)
.setPersonalEmails(personalEmails)
.setServiceKey(serviceKey)
.setSystemAdmin(systemAdmin)
.setGlobalGroups(globalGroups)
.setCollections(collections)
.setAccessTokens(tokens)
.setStatus(status)
.setDomain(domain)
.setMsExchDelegateListBL(msExchDelegateListBL)
.setDn(dn);
}
@Override
public UserCredential create(String username, String firstName, String lastName, String email, List<String> personalEmails, String serviceKey, boolean systemAdmin, List<String> globalGroups, List<String> collections, Map<String, LocalDateTime> tokens, UserCredentialStatus status, String domain, List<String> msExchDelegateListBL, String dn, String jobTitle, String phone, String fax, String address) {
return ((SolrUserCredential) valueOrDefault(getUserCredential(username), schemas.newCredential()))
.setUsername(cleanUsername(username))
.setFirstName(firstName)
.setLastName(lastName)
.setEmail(email)
.setPersonalEmails(personalEmails)
.setServiceKey(serviceKey)
.setSystemAdmin(systemAdmin)
.setGlobalGroups(globalGroups)
.setCollections(collections)
.setAccessTokens(tokens)
.setStatus(status)
.setDomain(domain)
.setMsExchDelegateListBL(msExchDelegateListBL)
.setDn(dn)
.withJobTitle(jobTitle)
.withAddress(address)
.withPhone(phone)
.withFax(fax);
}
@Override
public UserCredential create(String username, String firstName, String lastName, String email, String serviceKey,
boolean systemAdmin, List<String> globalGroups, List<String> collections, Map<String, LocalDateTime> tokens,
UserCredentialStatus status) {
return create(username, firstName, lastName, email, serviceKey, systemAdmin, globalGroups, collections, tokens, status,
null, null, null);
}
@Override
public UserCredential create(String username, String firstName, String lastName, String email, List<String> globalGroups,
List<String> collections, UserCredentialStatus status, String domain, List<String> msExchDelegateListBL, String dn) {
return create(username, firstName, lastName, email, null, false, globalGroups, collections,
Collections.<String, LocalDateTime>emptyMap(), status, domain, msExchDelegateListBL, dn);
}
@Override
public UserCredential create(String username, String firstName, String lastName, String email, List<String> globalGroups,
List<String> collections, UserCredentialStatus status) {
return create(username, firstName, lastName, email, null, false, globalGroups, collections,
Collections.<String, LocalDateTime>emptyMap(), status, null, null, null);
}
@Override
public void addUpdate(UserCredential userCredential) {
try {
modelLayerFactory.newRecordServices().add((SolrUserCredential) userCredential);
} catch (RecordServicesException e) {
throw new UserCredentialsManagerRuntimeException_CannotExecuteTransaction(e);
}
}
@Override
public UserCredential getUserCredential(String username) {
Record record = modelLayerFactory.newRecordServices()
.getRecordByMetadata(schemas.credentialUsername(), cleanUsername(username));
return record != null ? schemas.wrapCredential(record) : null;
}
public LogicalSearchQuery getUserCredentialsQuery() {
return new LogicalSearchQuery(from(schemas.credentialSchemaType()).returnAll()).sortAsc(schemas.credentialUsername());
}
@Override
public List<UserCredential> getUserCredentials() {
return schemas.wrapCredentials(searchServices.search(getUserCredentialsQuery()));
}
public LogicalSearchQuery getActiveUserCredentialsQuery() {
return getQueryFilteredByStatus(UserCredentialStatus.ACTIVE);
}
@Override
public List<UserCredential> getActiveUserCredentials() {
return schemas.wrapCredentials(searchServices.search(getActiveUserCredentialsQuery()));
}
public LogicalSearchQuery getSuspendedUserCredentialsQuery() {
return getQueryFilteredByStatus(UserCredentialStatus.SUSPENDED);
}
@Override
public List<UserCredential> getSuspendedUserCredentials() {
return schemas.wrapCredentials(searchServices.search(getSuspendedUserCredentialsQuery()));
}
public LogicalSearchQuery getPendingApprovalUserCredentialsQuery() {
return getQueryFilteredByStatus(UserCredentialStatus.PENDING);
}
@Override
public List<UserCredential> getPendingApprovalUserCredentials() {
return schemas.wrapCredentials(searchServices.search(getPendingApprovalUserCredentialsQuery()));
}
public LogicalSearchQuery getDeletedUserCredentialsQuery() {
return getQueryFilteredByStatus(UserCredentialStatus.DELETED);
}
@Override
public List<UserCredential> getDeletedUserCredentials() {
return schemas.wrapCredentials(searchServices.search(getDeletedUserCredentialsQuery()));
}
public LogicalSearchQuery getUserCredentialsInGlobalGroupQuery(String group) {
return new LogicalSearchQuery(from(schemas.credentialSchemaType()).where(schemas.credentialGroups()).isEqualTo(group))
.sortAsc(schemas.credentialUsername());
}
@Override
public List<UserCredential> getUserCredentialsInGlobalGroup(String group) {
return schemas.wrapCredentials(searchServices.search(getUserCredentialsInGlobalGroupQuery(group)));
}
public LogicalSearchQuery getUserCredentialsInCollectionQuery(String collection) {
return new LogicalSearchQuery(
from(schemas.credentialSchemaType()).where(schemas.credentialCollections()).isEqualTo(collection))
.sortAsc(schemas.credentialUsername());
}
@Override
public void removeCollection(final String collection) {
try {
new ActionExecutorInBatch(searchServices, "Remove collection in user credentials records", 100) {
@Override
public void doActionOnBatch(List<Record> records)
throws Exception {
Transaction transaction = new Transaction();
transaction.getRecordUpdateOptions().setOptimisticLockingResolution(EXCEPTION);
for (Record record : records) {
transaction.add((SolrUserCredential) schemas.wrapCredential(record).withRemovedCollection(collection));
}
modelLayerFactory.newRecordServices().execute(transaction);
}
}.execute(getUserCredentialsInCollectionQuery(collection));
} catch (Exception e) {
throw new UserCredentialsManagerRuntimeException_CannotExecuteTransaction(e);
}
}
@Override
public void removeToken(String token) {
UserCredential credential = getUserCredentialByToken(token);
if (credential != null) {
addUpdate(credential.withRemovedToken(token));
}
}
@Override
public void removeUserCredentialFromCollection(UserCredential userCredential, String collection) {
addUpdate(userCredential.withRemovedCollection(collection));
}
@Override
public void removeGroup(String group) {
Transaction transaction = new Transaction();
for (Record record : searchServices.search(getUserCredentialsInGlobalGroupQuery(group))) {
transaction.add((SolrUserCredential) schemas.wrapCredential(record).withRemovedGlobalGroup(group));
}
try {
modelLayerFactory.newRecordServices().execute(transaction);
} catch (RecordServicesException e) {
throw new UserCredentialsManagerRuntimeException_CannotExecuteTransaction(e);
}
}
public UserCredential getUserCredentialByServiceKey(String serviceKey) {
String encryptedKey = modelLayerFactory.newEncryptionServices().encrypt(serviceKey);
Record record = searchServices.searchSingleResult(
from(schemas.credentialSchemaType()).where(schemas.credentialServiceKey()).isEqualTo(encryptedKey));
return record != null ? schemas.wrapCredential(record) : null;
}
@Override
public String getUsernameByServiceKey(String serviceKey) {
UserCredential credential = getUserCredentialByServiceKey(serviceKey);
return credential != null ? credential.getUsername() : null;
}
public UserCredential getUserCredentialByToken(String token) {
String encryptedToken = modelLayerFactory.newEncryptionServices().encrypt(token);
Record record = searchServices.searchSingleResult(
from(schemas.credentialSchemaType()).where(schemas.credentialTokenKeys()).isEqualTo(encryptedToken));
return record != null ? schemas.wrapCredential(record) : null;
}
@Override
public String getServiceKeyByToken(String token) {
UserCredential credential = getUserCredentialByToken(token);
return credential != null ? credential.getServiceKey() : null;
}
@Override
public void removeTimedOutTokens() {
LocalDateTime now = TimeProvider.getLocalDateTime();
Transaction transaction = new Transaction();
for (Record record : searchServices.search(getUserCredentialsWithExpiredTokensQuery(now))) {
UserCredential credential = schemas.wrapCredential(record);
Map<String, LocalDateTime> validTokens = new HashMap<>();
for (Entry<String, LocalDateTime> token : credential.getAccessTokens().entrySet()) {
LocalDateTime expiration = token.getValue();
if (expiration.isAfter(now)) {
validTokens.put(token.getKey(), token.getValue());
}
}
transaction.add((SolrUserCredential) credential.withAccessTokens(validTokens));
}
try {
modelLayerFactory.newRecordServices().execute(transaction);
} catch (RecordServicesException e) {
throw new UserCredentialsManagerRuntimeException_CannotExecuteTransaction(e);
}
}
public LogicalSearchQuery getUserCredentialsWithExpiredTokensQuery(LocalDateTime now) {
return new LogicalSearchQuery(
from(schemas.credentialSchemaType()).where(schemas.credentialTokenExpirations()).isLessOrEqualThan(now));
}
@Override
public void rewrite() {
// Nothing to be done
}
@Override
public void initialize() {
BackgroundThreadsManager manager = modelLayerFactory.getDataLayerFactory().getBackgroundThreadsManager();
ModelLayerConfiguration configuration = modelLayerFactory.getConfiguration();
manager.configure(BackgroundThreadConfiguration.repeatingAction("removeTimedOutTokens", new Runnable() {
@Override
public void run() {
removeTimedOutTokens();
}
}).handlingExceptionWith(BackgroundThreadExceptionHandling.CONTINUE)
.executedEvery(configuration.getTokenRemovalThreadDelayBetweenChecks()));
}
@Override
public void close() {
// Nothing to be done
}
@Override
public void systemCollectionCreated() {
}
private LogicalSearchQuery getQueryFilteredByStatus(UserCredentialStatus status) {
return new LogicalSearchQuery(from(schemas.credentialSchemaType()).where(schemas.credentialStatus()).isEqualTo(status))
.sortAsc(schemas.credentialUsername());
}
}