/*
* (C) Copyright 2014-2015 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed 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.
*
* Contributors:
* mhilaire
*/
package org.nuxeo.ecm.directory.sql;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.security.auth.login.LoginContext;
import org.nuxeo.ecm.core.api.DataModel;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.NuxeoPrincipal;
import org.nuxeo.ecm.core.api.SystemPrincipal;
import org.nuxeo.ecm.core.api.impl.UserPrincipal;
import org.nuxeo.ecm.core.api.local.ClientLoginModule;
import org.nuxeo.ecm.core.api.local.LoginStack;
import org.nuxeo.ecm.core.test.CoreFeature;
import org.nuxeo.ecm.core.test.annotations.Granularity;
import org.nuxeo.ecm.directory.Directory;
import org.nuxeo.ecm.directory.DirectoryDeleteConstraintException;
import org.nuxeo.ecm.directory.DirectoryException;
import org.nuxeo.ecm.directory.Session;
import org.nuxeo.ecm.directory.api.DirectoryService;
import org.nuxeo.ecm.platform.login.test.ClientLoginFeature;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.test.runner.Deploy;
import org.nuxeo.runtime.test.runner.Features;
import org.nuxeo.runtime.test.runner.FeaturesRunner;
import org.nuxeo.runtime.test.runner.LocalDeploy;
import org.nuxeo.runtime.test.runner.SimpleFeature;
import org.nuxeo.runtime.transaction.TransactionHelper;
import com.google.inject.Binder;
import com.google.inject.Provider;
import com.google.inject.name.Names;
/**
* @since 6.0
*/
@Features({ CoreFeature.class, ClientLoginFeature.class })
@Deploy({ "org.nuxeo.ecm.directory.api", //
"org.nuxeo.ecm.directory", //
"org.nuxeo.ecm.core.schema", //
"org.nuxeo.ecm.directory.types.contrib", //
"org.nuxeo.ecm.directory.sql" })
@LocalDeploy("org.nuxeo.ecm.directory.sql:nxdirectory-ds.xml")
public class SQLDirectoryFeature extends SimpleFeature {
public static final String USER_DIRECTORY_NAME = "userDirectory";
public static final String GROUP_DIRECTORY_NAME = "groupDirectory";
@Override
public void configure(final FeaturesRunner runner, Binder binder) {
bindDirectory(binder, USER_DIRECTORY_NAME);
bindDirectory(binder, GROUP_DIRECTORY_NAME);
}
protected void bindDirectory(Binder binder, final String name) {
binder.bind(Directory.class).annotatedWith(Names.named(name)).toProvider(new Provider<Directory>() {
@Override
public Directory get() {
return Framework.getService(DirectoryService.class).getDirectory(name);
}
});
}
Granularity granularity;
protected Map<String, Map<String, Map<String, Object>>> allDirectoryData;
@Override
public void beforeRun(FeaturesRunner runner) throws Exception {
granularity = runner.getFeature(CoreFeature.class).getGranularity();
}
@Override
public void beforeSetup(FeaturesRunner runner) throws Exception {
if (granularity != Granularity.METHOD) {
return;
}
DirectoryService directoryService = Framework.getService(DirectoryService.class);
// record all directories in their entirety
allDirectoryData = new HashMap<>();
for (Directory dir : directoryService.getDirectories()) {
if (!isWritableSQLDirectory(dir)) {
continue;
}
Map<String, Map<String, Object>> data = new HashMap<>();
try (Session session = dir.getSession()) {
session.setReadAllColumns(true); // needs to fetch the password too
List<DocumentModel> entries = session.query(Collections.emptyMap(), Collections.emptySet(),
Collections.emptyMap(), true); // fetch references
for (DocumentModel entry : entries) {
DataModel dm = entry.getDataModel(dir.getSchema());
data.put(entry.getId(), dm.getMap());
}
allDirectoryData.put(dir.getName(), data);
}
}
// commit the transaction so that lazily-created relation tables are committed
if (TransactionHelper.isTransactionActiveOrMarkedRollback()) {
TransactionHelper.commitOrRollbackTransaction();
TransactionHelper.startTransaction();
}
}
@Override
public void afterTeardown(FeaturesRunner runner) throws Exception {
if (granularity != Granularity.METHOD) {
return;
}
if (allDirectoryData == null) {
// failure (exception or assumption failed) before any method was run
return;
}
// system user to bypass directory security
LoginStack loginStack = ClientLoginModule.getThreadLocalLogin();
loginStack.push(new SystemPrincipal(null), null, null);
try {
DirectoryService directoryService = Framework.getService(DirectoryService.class);
// clear all directories
boolean isAllClear = true;
do {
isAllClear = true;
for (Directory dir : directoryService.getDirectories()) {
if (!isWritableSQLDirectory(dir)) {
continue;
}
try (Session session = dir.getSession()) {
List<String> ids = session.getProjection(Collections.emptyMap(), dir.getIdField());
for (String id : ids) {
try {
session.deleteEntry(id);
} catch(DirectoryDeleteConstraintException e) {
isAllClear = false;
}
}
}
}
} while(!isAllClear);
// re-create all directory entries
for (Entry<String, Map<String, Map<String, Object>>> each : allDirectoryData.entrySet()) {
String directoryName = each.getKey();
Directory directory = directoryService.getDirectory(directoryName);
Collection<Map<String, Object>> data = each.getValue().values();
try (Session session = directory.getSession()) {
for (Map<String, Object> map : data) {
try {
session.createEntry(map);
} catch (DirectoryException e) {
// happens for filter directories
// or when testing config changes
if (!e.getMessage().contains("already exists") && !e.getMessage().contains("Missing id")) {
throw e;
}
}
}
}
}
} finally {
loginStack.pop();
}
allDirectoryData = null;
}
public boolean isWritableSQLDirectory(Directory dir) {
if (!(dir instanceof SQLDirectory)) {
return false;
}
SQLDirectoryDescriptor descriptor = ((SQLDirectory) dir).getDescriptor();
if (descriptor != null && descriptor.isReadOnly()) {
return false;
}
return true;
}
}