Add initial audit log API

Introduce audit log API which logs all creations,
modifications and deletions of annotated entities
and everything which is stored inside a
ConfigurationStore. Without the related Audit
Log Plugin installed this API does nothing.
This commit is contained in:
Eduard Heimbuch
2023-03-09 11:25:33 +01:00
committed by SCM-Manager
parent e74225e168
commit 56265be9a2
36 changed files with 590 additions and 178 deletions

View File

@@ -21,12 +21,16 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm;
import com.github.sdorra.ssp.PermissionCheck;
import sonia.scm.auditlog.AuditEntry;
import sonia.scm.auditlog.Auditor;
import sonia.scm.auditlog.EntryCreationContext;
import sonia.scm.util.AssertUtil;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -34,9 +38,11 @@ import java.util.function.Supplier;
public class ManagerDaoAdapter<T extends ModelObject> {
private final GenericDAO<T> dao;
private final Set<Auditor> auditors;
public ManagerDaoAdapter(GenericDAO<T> dao) {
public ManagerDaoAdapter(GenericDAO<T> dao, Set<Auditor> auditors) {
this.dao = dao;
this.auditors = auditors;
}
public void modify(T object, Function<T, PermissionCheck> permissionCheck, AroundHandler<T> beforeUpdate, AroundHandler<T> afterUpdate) {
@@ -51,6 +57,8 @@ public class ManagerDaoAdapter<T extends ModelObject> {
object.setLastModified(System.currentTimeMillis());
object.setCreationDate(notModified.getCreationDate());
callAuditors(notModified, object);
dao.modify(object);
afterUpdate.handle(notModified);
@@ -73,6 +81,7 @@ public class ManagerDaoAdapter<T extends ModelObject> {
existsCheck.accept(newObject);
newObject.setCreationDate(System.currentTimeMillis());
beforeCreate.handle(newObject);
callAuditors(null, newObject);
dao.add(newObject);
afterCreate.handle(newObject);
return newObject;
@@ -82,6 +91,7 @@ public class ManagerDaoAdapter<T extends ModelObject> {
permissionCheck.get().check();
if (dao.contains(toDelete)) {
beforeDelete.handle(toDelete);
callAuditors(toDelete, null);
dao.delete(toDelete);
afterDelete.handle(toDelete);
} else {
@@ -89,6 +99,12 @@ public class ManagerDaoAdapter<T extends ModelObject> {
}
}
private void callAuditors(T notModified, T newObject) {
if ((newObject == null? notModified: newObject).getClass().isAnnotationPresent(AuditEntry.class)) {
auditors.forEach(s -> s.createEntry(new EntryCreationContext<>(newObject, notModified)));
}
}
@FunctionalInterface
public interface AroundHandler<T extends ModelObject> {
void handle(T notModified);

View File

@@ -37,6 +37,7 @@ import sonia.scm.HandlerEventType;
import sonia.scm.ManagerDaoAdapter;
import sonia.scm.NotFoundException;
import sonia.scm.SCMContextProvider;
import sonia.scm.auditlog.Auditor;
import sonia.scm.search.SearchRequest;
import sonia.scm.search.SearchUtil;
import sonia.scm.util.CollectionAppender;
@@ -77,10 +78,10 @@ public class DefaultGroupManager extends AbstractGroupManager
* @param groupDAO
*/
@Inject
public DefaultGroupManager(GroupDAO groupDAO)
public DefaultGroupManager(GroupDAO groupDAO, Set<Auditor> auditors)
{
this.groupDAO = groupDAO;
this.managerDaoAdapter = new ManagerDaoAdapter<>(groupDAO);
this.managerDaoAdapter = new ManagerDaoAdapter<>(groupDAO, auditors);
}
//~--- methods --------------------------------------------------------------
@@ -285,16 +286,11 @@ public class DefaultGroupManager extends AbstractGroupManager
final PermissionActionCheck<Group> check = GroupPermissions.read();
return Util.createSubCollection(groupDAO.getAll(), comparator,
new CollectionAppender<Group>()
{
@Override
public void append(Collection<Group> collection, Group group)
{
(collection, group) -> {
if (check.isPermitted(group)) {
collection.add(group.clone());
}
}
}, start, limit);
}, start, limit);
}
/**

View File

@@ -26,6 +26,7 @@ package sonia.scm.lifecycle.modules;
import com.google.inject.AbstractModule;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.throwingproviders.ThrowingProviderBinder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,6 +50,8 @@ import sonia.scm.security.DefaultKeyGenerator;
import sonia.scm.security.KeyGenerator;
import sonia.scm.store.BlobStoreFactory;
import sonia.scm.store.ConfigurationEntryStoreFactory;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.ConfigurationStoreDecoratorFactory;
import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.store.DataStoreFactory;
import sonia.scm.store.DefaultBlobDirectoryAccess;
@@ -100,6 +103,10 @@ public class BootstrapModule extends AbstractModule {
// note CipherUtil uses an other generator
bind(CipherHandler.class).toInstance(CipherUtil.getInstance().getCipherHandler());
// Bind empty set in the bootstrap module
Multibinder.newSetBinder(binder(), ConfigurationStoreDecoratorFactory.class).addBinding()
.to(NoOpConfigurationStoreDecoratorFactory.class);
// bind core
bind(RepositoryArchivedCheck.class, EventDrivenRepositoryArchiveCheck.class);
bind(RepositoryExportingCheck.class, DefaultRepositoryExportingCheck.class);
@@ -137,4 +144,11 @@ public class BootstrapModule extends AbstractModule {
return implementation;
}
private static class NoOpConfigurationStoreDecoratorFactory implements ConfigurationStoreDecoratorFactory {
@Override
public <T> ConfigurationStore<T> createDecorator(ConfigurationStore<T> object, Context context) {
return object;
}
}
}

View File

@@ -44,6 +44,7 @@ import sonia.scm.api.v2.resources.BranchLinkProvider;
import sonia.scm.api.v2.resources.DefaultBranchLinkProvider;
import sonia.scm.api.v2.resources.DefaultRepositoryLinkProvider;
import sonia.scm.api.v2.resources.RepositoryLinkProvider;
import sonia.scm.auditlog.AuditLogConfigurationStoreDecoratorFactory;
import sonia.scm.cache.CacheManager;
import sonia.scm.cache.GuavaCacheManager;
import sonia.scm.config.ScmConfiguration;
@@ -98,6 +99,7 @@ import sonia.scm.security.DefaultSecuritySystem;
import sonia.scm.security.LoginAttemptHandler;
import sonia.scm.security.RepositoryPermissionProvider;
import sonia.scm.security.SecuritySystem;
import sonia.scm.store.ConfigurationStoreDecoratorFactory;
import sonia.scm.store.FileStoreExporter;
import sonia.scm.store.StoreExporter;
import sonia.scm.template.MustacheTemplateEngine;
@@ -154,6 +156,10 @@ class ScmServletModule extends ServletModule {
bind(NamespaceStrategy.class).toProvider(NamespaceStrategyProvider.class);
// bind store decorators
Multibinder<ConfigurationStoreDecoratorFactory> storeDecoratorMultiBinder = Multibinder.newSetBinder(binder(), ConfigurationStoreDecoratorFactory.class);
storeDecoratorMultiBinder.addBinding().to(AuditLogConfigurationStoreDecoratorFactory.class);
// bind repository provider
ThrowingProviderBinder.create(binder())
.bind(RepositoryProvider.class, Repository.class)

View File

@@ -37,6 +37,7 @@ import sonia.scm.NoChangesMadeException;
import sonia.scm.NotFoundException;
import sonia.scm.SCMContextProvider;
import sonia.scm.Type;
import sonia.scm.auditlog.Auditor;
import sonia.scm.event.ScmEventBus;
import sonia.scm.security.AuthorizationChangedEvent;
import sonia.scm.security.KeyGenerator;
@@ -112,9 +113,13 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
private final RepositoryPostProcessor repositoryPostProcessor;
@Inject
public DefaultRepositoryManager(SCMContextProvider contextProvider, KeyGenerator keyGenerator,
RepositoryDAO repositoryDAO, Set<RepositoryHandler> handlerSet,
Provider<NamespaceStrategy> namespaceStrategyProvider, RepositoryPostProcessor repositoryPostProcessor) {
public DefaultRepositoryManager(SCMContextProvider contextProvider,
KeyGenerator keyGenerator,
RepositoryDAO repositoryDAO,
Set<RepositoryHandler> handlerSet,
Provider<NamespaceStrategy> namespaceStrategyProvider,
RepositoryPostProcessor repositoryPostProcessor,
Set<Auditor> auditors) {
this.keyGenerator = keyGenerator;
this.repositoryDAO = repositoryDAO;
this.namespaceStrategyProvider = namespaceStrategyProvider;
@@ -126,7 +131,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
for (RepositoryHandler handler : handlerSet) {
addHandler(contextProvider, handler);
}
managerDaoAdapter = new ManagerDaoAdapter<>(repositoryDAO);
managerDaoAdapter = new ManagerDaoAdapter<>(repositoryDAO, auditors);
}
@Override

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository;
import com.google.inject.Inject;
@@ -34,31 +34,35 @@ import sonia.scm.HandlerEventType;
import sonia.scm.ManagerDaoAdapter;
import sonia.scm.NotFoundException;
import sonia.scm.SCMContextProvider;
import sonia.scm.auditlog.Auditor;
import sonia.scm.security.RepositoryPermissionProvider;
import sonia.scm.util.Util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Singleton @EagerSingleton
public class DefaultRepositoryRoleManager extends AbstractRepositoryRoleManager
{
@Singleton
@EagerSingleton
public class DefaultRepositoryRoleManager extends AbstractRepositoryRoleManager {
/** the logger for XmlRepositoryRoleManager */
/**
* the logger for XmlRepositoryRoleManager
*/
private static final Logger logger =
LoggerFactory.getLogger(DefaultRepositoryRoleManager.class);
@Inject
public DefaultRepositoryRoleManager(RepositoryRoleDAO repositoryRoleDAO, RepositoryPermissionProvider repositoryPermissionProvider)
{
public DefaultRepositoryRoleManager(RepositoryRoleDAO repositoryRoleDAO,
RepositoryPermissionProvider repositoryPermissionProvider,
Set<Auditor> auditors) {
this.repositoryRoleDAO = repositoryRoleDAO;
this.managerDaoAdapter = new ManagerDaoAdapter<>(repositoryRoleDAO);
this.managerDaoAdapter = new ManagerDaoAdapter<>(repositoryRoleDAO, auditors);
this.repositoryPermissionProvider = repositoryPermissionProvider;
}
@@ -99,6 +103,7 @@ public class DefaultRepositoryRoleManager extends AbstractRepositoryRoleManager
@Override
public void init(SCMContextProvider context) {
// Nothing
}
@Override
@@ -179,20 +184,16 @@ public class DefaultRepositoryRoleManager extends AbstractRepositoryRoleManager
@Override
public Collection<RepositoryRole> getAll(Comparator<RepositoryRole> comaparator, int start, int limit) {
return Util.createSubCollection(getAll(), comaparator,
(collection, item) -> {
collection.add(item.clone());
}, start, limit);
(collection, item) -> collection.add(item.clone()), start, limit);
}
@Override
public Collection<RepositoryRole> getAll(int start, int limit)
{
public Collection<RepositoryRole> getAll(int start, int limit) {
return getAll(null, start, limit);
}
@Override
public Long getLastModified()
{
public Long getLastModified() {
return repositoryRoleDAO.getLastModified();
}

View File

@@ -37,6 +37,7 @@ import sonia.scm.HandlerEventType;
import sonia.scm.ManagerDaoAdapter;
import sonia.scm.NotFoundException;
import sonia.scm.SCMContextProvider;
import sonia.scm.auditlog.Auditor;
import sonia.scm.search.SearchRequest;
import sonia.scm.search.SearchUtil;
import sonia.scm.security.Authentications;
@@ -48,6 +49,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
/**
@@ -76,11 +78,11 @@ public class DefaultUserManager extends AbstractUserManager
* @param userDAO
*/
@Inject
public DefaultUserManager(PasswordService passwordService, UserDAO userDAO)
public DefaultUserManager(PasswordService passwordService, UserDAO userDAO, Set<Auditor> auditors)
{
this.passwordService = passwordService;
this.userDAO = userDAO;
this.managerDaoAdapter = new ManagerDaoAdapter<>(userDAO);
this.managerDaoAdapter = new ManagerDaoAdapter<>(userDAO, auditors);
}
//~--- methods --------------------------------------------------------------