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

@@ -0,0 +1,2 @@
- type: added
description: Initial implementation of an audit log API

View File

@@ -0,0 +1,38 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.auditlog;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.LOCAL_VARIABLE, ElementType.FIELD, ElementType.PACKAGE, ElementType.METHOD})
public @interface AuditEntry {
String[] labels() default {};
String[] maskedFields() default {};
String[] ignoredFields() default {};
}

View File

@@ -0,0 +1,62 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.auditlog;
import com.google.common.base.Strings;
import sonia.scm.repository.RepositoryDAO;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.StoreDecoratorFactory;
import java.util.Set;
public class AuditLogConfigurationStoreDecorator<T> implements ConfigurationStore<T> {
private final Set<Auditor> auditors;
private final RepositoryDAO repositoryDAO;
private final ConfigurationStore<T> delegate;
private final StoreDecoratorFactory.Context context;
public AuditLogConfigurationStoreDecorator(Set<Auditor> auditors, RepositoryDAO repositoryDAO, ConfigurationStore<T> delegate, StoreDecoratorFactory.Context context) {
this.auditors = auditors;
this.repositoryDAO = repositoryDAO;
this.delegate = delegate;
this.context = context;
}
public T get() {
return delegate.get();
}
public void set(T object) {
String repositoryId = context.getStoreParameters().getRepositoryId();
if (!Strings.isNullOrEmpty(repositoryId)) {
String name = repositoryDAO.get(repositoryId).getNamespaceAndName().toString();
auditors.forEach(s -> s.createEntry(new EntryCreationContext<>(object, get(), name, Set.of("repository"))));
} else {
auditors.forEach(s -> s.createEntry(new EntryCreationContext<>(object, get(), "", Set.of(context.getStoreParameters().getName()))));
}
delegate.set(object);
}
}

View File

@@ -0,0 +1,49 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.auditlog;
import sonia.scm.repository.RepositoryDAO;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.ConfigurationStoreDecoratorFactory;
import javax.inject.Inject;
import java.util.Set;
public class AuditLogConfigurationStoreDecoratorFactory implements ConfigurationStoreDecoratorFactory {
private final Set<Auditor> auditors;
private final RepositoryDAO repositoryDAO;
@Inject
public AuditLogConfigurationStoreDecoratorFactory(Set<Auditor> auditor, RepositoryDAO repositoryDAO) {
this.auditors = auditor;
this.repositoryDAO = repositoryDAO;
}
@Override
public <T> ConfigurationStore<T> createDecorator(ConfigurationStore<T> object, Context context) {
return new AuditLogConfigurationStoreDecorator<>(auditors, repositoryDAO, object, context);
}
}

View File

@@ -0,0 +1,29 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.auditlog;
public interface AuditLogEntity {
String getEntityName();
}

View File

@@ -0,0 +1,32 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.auditlog;
import sonia.scm.plugin.ExtensionPoint;
@ExtensionPoint
public interface Auditor {
void createEntry(EntryCreationContext<?> context);
}

View File

@@ -0,0 +1,54 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.auditlog;
import lombok.Getter;
import java.util.Set;
import static java.util.Collections.emptySet;
@Getter
public class EntryCreationContext<T> {
private final T object;
private final T oldObject;
private final String entity;
private final Set<String> additionalLabels;
public EntryCreationContext(T object, T oldObject) {
this(object, oldObject, "", emptySet());
}
public EntryCreationContext(T object, T oldObject, Set<String> additionalLabels) {
this(object, oldObject, "", additionalLabels);
}
public EntryCreationContext(T object, T oldObject, String entity, Set<String> additionalLabels) {
this.object = object;
this.oldObject = oldObject;
this.entity = entity;
this.additionalLabels = additionalLabels;
}
}

View File

@@ -29,6 +29,7 @@ import com.google.common.collect.Sets;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.auditlog.AuditEntry;
import sonia.scm.event.ScmEventBus;
import sonia.scm.security.AnonymousMode;
import sonia.scm.util.HttpUtil;
@@ -57,6 +58,7 @@ import java.util.concurrent.TimeUnit;
@Singleton
@XmlRootElement(name = "scm-config")
@XmlAccessorType(XmlAccessType.FIELD)
@AuditEntry(labels = "config", maskedFields = "proxyPassword")
public class ScmConfiguration implements Configuration {
/**

View File

@@ -34,6 +34,8 @@ import com.google.common.collect.Lists;
import sonia.scm.BasicPropertiesAware;
import sonia.scm.ModelObject;
import sonia.scm.ReducedModelObject;
import sonia.scm.auditlog.AuditEntry;
import sonia.scm.auditlog.AuditLogEntity;
import sonia.scm.search.Indexed;
import sonia.scm.search.IndexedType;
import sonia.scm.util.Util;
@@ -50,7 +52,7 @@ import java.util.List;
/**
* Organizes users into a group for easier permissions management.
*
* <p>
* TODO for 2.0: Use a set instead of a list for members
*
* @author Sebastian Sdorra
@@ -63,31 +65,30 @@ import java.util.List;
)
@XmlRootElement(name = "groups")
@XmlAccessorType(XmlAccessType.FIELD)
@AuditEntry(labels = "group", ignoredFields = "lastModified")
public class Group extends BasicPropertiesAware
implements ModelObject, PermissionObject, ReducedModelObject
{
implements ModelObject, PermissionObject, ReducedModelObject, AuditLogEntity {
/** Field description */
/**
* Field description
*/
private static final long serialVersionUID = 1752369869345245872L;
//~--- constructors ---------------------------------------------------------
/**
* Constructs {@link Group} object. This constructor is required by JAXB.
*
*/
public Group() {}
public Group() {
}
/**
* Constructs {@link Group} object.
*
*
*
* @param type of the group
* @param name of the group
*/
public Group(String type, String name)
{
public Group(String type, String name) {
this.type = type;
this.name = name;
this.members = Lists.newArrayList();
@@ -96,14 +97,11 @@ public class Group extends BasicPropertiesAware
/**
* Constructs {@link Group} object.
*
*
*
* @param type of the group
* @param name of the group
* @param type of the group
* @param name of the group
* @param members of the groups
*/
public Group(String type, String name, List<String> members)
{
public Group(String type, String name, List<String> members) {
this.type = type;
this.name = name;
this.members = members;
@@ -112,20 +110,16 @@ public class Group extends BasicPropertiesAware
/**
* Constructs {@link Group} object.
*
*
*
* @param type of the group
* @param name of the group
* @param type of the group
* @param name of the group
* @param members of the groups
*/
public Group(String type, String name, String... members)
{
public Group(String type, String name, String... members) {
this.type = type;
this.name = name;
this.members = Lists.newArrayList();
if (Util.isNotEmpty(members))
{
if (Util.isNotEmpty(members)) {
this.members.addAll(Arrays.asList(members));
}
}
@@ -135,43 +129,32 @@ public class Group extends BasicPropertiesAware
/**
* Add a new member to the group.
*
*
* @param member - The name of new group member
*
* @return true if the operation was successful
*/
public boolean add(String member)
{
public boolean add(String member) {
return getMembers().add(member);
}
/**
* Remove all members of the group.
*
*/
public void clear()
{
public void clear() {
members.clear();
}
/**
* Returns a clone of the group.
*
*
* @return a clone of the group
*
*/
@Override
public Group clone()
{
public Group clone() {
Group group = null;
try
{
try {
group = (Group) super.clone();
}
catch (CloneNotSupportedException ex)
{
} catch (CloneNotSupportedException ex) {
throw new RuntimeException(ex);
}
@@ -181,11 +164,9 @@ public class Group extends BasicPropertiesAware
/**
* Copies all properties of this group to the given one.
*
*
* @param group to copies all properties of this one
*/
public void copyProperties(Group group)
{
public void copyProperties(Group group) {
group.setName(name);
group.setMembers(members);
group.setType(type);
@@ -196,21 +177,16 @@ public class Group extends BasicPropertiesAware
/**
* Returns true if this {@link Group} is the same as the obj argument.
*
*
* @param obj - the reference object with which to compare
*
* @return true if this {@link Group} is the same as the obj argument
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass())
{
if (getClass() != obj.getClass()) {
return false;
}
@@ -229,12 +205,10 @@ public class Group extends BasicPropertiesAware
/**
* Returns a hash code value for this {@link Group}.
*
*
* @return a hash code value for this {@link Group}
*/
@Override
public int hashCode()
{
public int hashCode() {
return Objects.hashCode(name, description, members, type, creationDate,
lastModified, properties);
}
@@ -242,36 +216,31 @@ public class Group extends BasicPropertiesAware
/**
* Remove the given member from this group.
*
*
* @param member to remove from this group
*
* @return true if the operation was successful
*/
public boolean remove(String member)
{
public boolean remove(String member) {
return members.remove(member);
}
/**
* Returns a {@link String} that represents this group.
*
*
* @return a {@link String} that represents this group
*/
@Override
public String toString()
{
public String toString() {
//J-
return MoreObjects.toStringHelper(this)
.add("name", name)
.add("description", description)
.add("members", members)
.add("type", type)
.add("external", external)
.add("creationDate", creationDate)
.add("lastModified", lastModified)
.add("properties", properties)
.toString();
.add("name", name)
.add("description", description)
.add("members", members)
.add("type", type)
.add("external", external)
.add("creationDate", creationDate)
.add("lastModified", lastModified)
.add("properties", properties)
.toString();
//J+
}
@@ -280,22 +249,18 @@ public class Group extends BasicPropertiesAware
/**
* Returns a timestamp of the creation date of this group.
*
*
* @return a timestamp of the creation date of this group
*/
public Long getCreationDate()
{
public Long getCreationDate() {
return creationDate;
}
/**
* Returns the description of this group.
*
*
* @return the description of this group
* @return the description of this group
*/
public String getDescription()
{
public String getDescription() {
return description;
}
@@ -303,12 +268,10 @@ public class Group extends BasicPropertiesAware
* Returns the unique name of this group. This method is an alias for the
* {@link #getName()} method.
*
*
* @return the unique name of this group
*/
@Override
public String getId()
{
public String getId() {
return name;
}
@@ -320,23 +283,19 @@ public class Group extends BasicPropertiesAware
/**
* Returns a timestamp of the last modified date of this group.
*
*
* @return a timestamp of the last modified date of this group
*/
@Override
public Long getLastModified()
{
public Long getLastModified() {
return lastModified;
}
/**
* Returns a {@link java.util.List} of all members of this group.
*
*
* @return a {@link java.util.List} of all members of this group
*/
public List<String> getMembers()
{
public List<String> getMembers() {
if (external) {
return Collections.emptyList();
} else if (members == null) {
@@ -349,23 +308,19 @@ public class Group extends BasicPropertiesAware
/**
* Returns the unique name of this group.
*
*
* @return the unique name of this group
*/
public String getName()
{
public String getName() {
return name;
}
/**
* Returns the type of this group. The default type is xml.
*
*
* @return the type of this group
*/
@Override
public String getType()
{
public String getType() {
return type;
}
@@ -381,25 +336,20 @@ public class Group extends BasicPropertiesAware
/**
* Returns true if the member is a member of this group.
*
*
* @param member - The name of the member
*
* @return true if the member is a member of this group
*/
public boolean isMember(String member)
{
public boolean isMember(String member) {
return (members != null) && members.contains(member);
}
/**
* Returns true if the group is valid.
*
*
* @return true if the group is valid
*/
@Override
public boolean isValid()
{
public boolean isValid() {
return ValidationUtil.isNameValid(name) && Util.isNotEmpty(type);
}
@@ -408,66 +358,54 @@ public class Group extends BasicPropertiesAware
/**
* Sets the date the group was created.
*
*
* @param creationDate - date the group was last modified
*/
public void setCreationDate(Long creationDate)
{
public void setCreationDate(Long creationDate) {
this.creationDate = creationDate;
}
/**
* Sets the description of the group.
*
*
* @param description of the group
*/
public void setDescription(String description)
{
public void setDescription(String description) {
this.description = description;
}
/**
* Sets the date the group was last modified.
*
*
* @param lastModified - date the group was last modified
*/
public void setLastModified(Long lastModified)
{
public void setLastModified(Long lastModified) {
this.lastModified = lastModified;
}
/**
* Sets the members of the group.
*
*
* @param members of the group
*/
public void setMembers(List<String> members)
{
public void setMembers(List<String> members) {
this.members = members;
}
/**
* Sets the name of the group.
*
*
* @param name of the group
*/
public void setName(String name)
{
public void setName(String name) {
this.name = name;
}
/**
* Sets the type of the group.
*
*
* @param type of the group
*/
public void setType(String type)
{
public void setType(String type) {
this.type = type;
}
@@ -476,35 +414,57 @@ public class Group extends BasicPropertiesAware
*
* @param {@code true} for a external group
*/
public void setExternal(boolean external)
{
public void setExternal(boolean external) {
this.external = external;
}
//~--- fields ---------------------------------------------------------------
/** external group */
/**
* external group
*/
private boolean external = false;
/** timestamp of the creation date of this group */
/**
* timestamp of the creation date of this group
*/
@Indexed
private Long creationDate;
/** description of this group */
/**
* description of this group
*/
@Indexed(defaultQuery = true, highlighted = true)
private String description;
/** timestamp of the last modified date of this group */
/**
* timestamp of the last modified date of this group
*/
@Indexed
private Long lastModified;
/** members of this group */
/**
* members of this group
*/
private List<String> members;
/** name of this group */
/**
* name of this group
*/
@Indexed(defaultQuery = true, boost = 1.5f)
private String name;
/** type of this group */
/**
* type of this group
*/
private String type;
/**
* Get the entity name which is used for the audit log
* @since 2.43.0
*/
@Override
public String getEntityName() {
return getName();
}
}

View File

@@ -28,6 +28,7 @@ import com.github.sdorra.ssp.PermissionObject;
import com.github.sdorra.ssp.StaticPermissions;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import sonia.scm.auditlog.AuditEntry;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@@ -46,6 +47,7 @@ import static java.util.Collections.unmodifiableCollection;
)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "namespaces")
@AuditEntry(labels = "namespace")
public class Namespace implements PermissionObject, Cloneable, RepositoryPermissionHolder {
private String namespace;

View File

@@ -31,6 +31,8 @@ import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import sonia.scm.BasicPropertiesAware;
import sonia.scm.ModelObject;
import sonia.scm.auditlog.AuditEntry;
import sonia.scm.auditlog.AuditLogEntity;
import sonia.scm.search.Indexed;
import sonia.scm.search.IndexedType;
import sonia.scm.util.Util;
@@ -64,9 +66,10 @@ import java.util.Set;
@Guard(guard = RepositoryPermissionGuard.class)
}
)
@AuditEntry(labels = "repository", ignoredFields = "lastModified")
public class Repository
extends BasicPropertiesAware
implements ModelObject, PermissionObject, RepositoryCoordinates, RepositoryPermissionHolder {
implements ModelObject, PermissionObject, RepositoryCoordinates, RepositoryPermissionHolder, AuditLogEntity {
private static final long serialVersionUID = 3486560714961909711L;
@@ -413,4 +416,13 @@ public class Repository
.add("healthCheckFailures", healthCheckFailures)
.toString();
}
/**
* Get the entity name which is used for the audit log
* @since 2.43.0
*/
@Override
public String getEntityName() {
return getNamespaceAndName().toString();
}
}

View File

@@ -30,6 +30,8 @@ import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
import sonia.scm.ModelObject;
import sonia.scm.auditlog.AuditEntry;
import sonia.scm.auditlog.AuditLogEntity;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@@ -51,7 +53,8 @@ import static java.util.Collections.unmodifiableSet;
@StaticPermissions(value = "repositoryRole", permissions = {}, globalPermissions = {"write"})
@XmlRootElement(name = "roles")
@XmlAccessorType(XmlAccessType.FIELD)
public class RepositoryRole implements ModelObject, PermissionObject {
@AuditEntry(labels = "role", ignoredFields = "lastModified")
public class RepositoryRole implements ModelObject, PermissionObject, AuditLogEntity {
private static final long serialVersionUID = -723588336073192740L;
@@ -218,4 +221,9 @@ public class RepositoryRole implements ModelObject, PermissionObject {
throw new RuntimeException(ex);
}
}
@Override
public String getEntityName() {
return getId();
}
}

View File

@@ -28,6 +28,7 @@ package sonia.scm.security;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import sonia.scm.auditlog.AuditEntry;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@@ -45,6 +46,7 @@ import java.io.Serializable;
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "assigned-permission")
@AuditEntry(labels = "permission")
public class AssignedPermission implements PermissionObject, Serializable
{

View File

@@ -0,0 +1,29 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.store;
public interface ConfigurationStoreDecoratorFactory extends StoreDecoratorFactory {
<T> ConfigurationStore<T> createDecorator(ConfigurationStore<T> object, Context context);
}

View File

@@ -0,0 +1,41 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.store;
import lombok.AllArgsConstructor;
import lombok.Getter;
public interface StoreDecoratorFactory {
<T> ConfigurationStore<T> createDecorator(ConfigurationStore<T> object, Context context);
@AllArgsConstructor
@Getter
class Context {
private TypedStoreParameters<?> storeParameters;
}
}

View File

@@ -35,6 +35,8 @@ import lombok.Setter;
import sonia.scm.BasicPropertiesAware;
import sonia.scm.ModelObject;
import sonia.scm.ReducedModelObject;
import sonia.scm.auditlog.AuditEntry;
import sonia.scm.auditlog.AuditLogEntity;
import sonia.scm.search.Indexed;
import sonia.scm.search.IndexedType;
import sonia.scm.util.Util;
@@ -58,7 +60,8 @@ import java.security.Principal;
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User extends BasicPropertiesAware implements Principal, ModelObject, PermissionObject, ReducedModelObject {
@AuditEntry(labels = "user", maskedFields = "password", ignoredFields = "lastModified")
public class User extends BasicPropertiesAware implements Principal, ModelObject, PermissionObject, ReducedModelObject, AuditLogEntity {
private static final long serialVersionUID = -3089541936726329663L;
@@ -225,4 +228,13 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject
public String getId() {
return name;
}
/**
* Get the entity name which is used for the audit log
* @since 2.43.0
*/
@Override
public String getEntityName() {
return getName();
}
}

View File

@@ -30,6 +30,8 @@ import sonia.scm.SCMContextProvider;
import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.repository.RepositoryReadOnlyChecker;
import java.util.Set;
/**
* JAXB implementation of {@link ConfigurationStoreFactory}.
*
@@ -38,20 +40,24 @@ import sonia.scm.repository.RepositoryReadOnlyChecker;
@Singleton
public class JAXBConfigurationStoreFactory extends FileBasedStoreFactory implements ConfigurationStoreFactory {
private final Set<ConfigurationStoreDecoratorFactory> decoratorFactories;
/**
* Constructs a new instance.
*
* @param repositoryLocationResolver Resolver to get the repository Directory
*/
@Inject
public JAXBConfigurationStoreFactory(SCMContextProvider contextProvider, RepositoryLocationResolver repositoryLocationResolver, RepositoryReadOnlyChecker readOnlyChecker) {
public JAXBConfigurationStoreFactory(SCMContextProvider contextProvider, RepositoryLocationResolver repositoryLocationResolver, RepositoryReadOnlyChecker readOnlyChecker, Set<ConfigurationStoreDecoratorFactory> decoratorFactories) {
super(contextProvider, repositoryLocationResolver, Store.CONFIG, readOnlyChecker);
this.decoratorFactories = decoratorFactories;
}
@Override
public <T> JAXBConfigurationStore<T> getStore(TypedStoreParameters<T> storeParameters) {
public <T> ConfigurationStore<T> getStore(TypedStoreParameters<T> storeParameters) {
TypedStoreContext<T> context = TypedStoreContext.of(storeParameters);
return new JAXBConfigurationStore<>(
ConfigurationStore<T> store = new JAXBConfigurationStore<>(
context,
storeParameters.getType(),
getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION),
@@ -59,5 +65,11 @@ public class JAXBConfigurationStoreFactory extends FileBasedStoreFactory impleme
storeParameters.getRepositoryId()),
() -> mustBeReadOnly(storeParameters)
);
for (ConfigurationStoreDecoratorFactory factory : decoratorFactories) {
store = factory.createDecorator(store, new StoreDecoratorFactory.Context(storeParameters));
}
return store;
}
}

View File

@@ -25,9 +25,13 @@
package sonia.scm.store;
import org.junit.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryReadOnlyChecker;
import java.util.Collections;
import static java.util.Collections.emptySet;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -46,7 +50,7 @@ public class JAXBConfigurationStoreTest extends StoreTestBase {
@Override
protected JAXBConfigurationStoreFactory createStoreFactory() {
return new JAXBConfigurationStoreFactory(contextProvider, repositoryLocationResolver, readOnlyChecker);
return new JAXBConfigurationStoreFactory(contextProvider, repositoryLocationResolver, readOnlyChecker, emptySet());
}

View File

@@ -27,6 +27,7 @@ package sonia.scm.repository;
//~--- JDK imports ------------------------------------------------------------
import com.google.common.base.Strings;
import sonia.scm.auditlog.AuditEntry;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@@ -39,6 +40,7 @@ import javax.xml.bind.annotation.XmlTransient;
*/
@XmlRootElement(name = "config")
@XmlAccessorType(XmlAccessType.FIELD)
@AuditEntry(labels = {"git", "config"})
public class GitConfig extends RepositoryConfig {
private static final String FALLBACK_BRANCH = "main";

View File

@@ -24,12 +24,15 @@
package sonia.scm.repository;
import sonia.scm.auditlog.AuditEntry;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "config")
@XmlAccessorType(XmlAccessType.FIELD)
@AuditEntry(labels = {"git", "config"})
public class GitRepositoryConfig {
public GitRepositoryConfig() {

View File

@@ -25,10 +25,12 @@
package sonia.scm.repository;
import lombok.Value;
import sonia.scm.auditlog.AuditEntry;
import java.io.File;
@Value
@AuditEntry(labels = {"hg", "config"})
public class HgConfig {
String hgBinary;

View File

@@ -25,6 +25,7 @@
package sonia.scm.repository;
import sonia.scm.auditlog.AuditEntry;
import sonia.scm.util.Util;
import javax.xml.bind.annotation.XmlRootElement;
@@ -36,6 +37,7 @@ import javax.xml.bind.annotation.XmlTransient;
* @author Sebastian Sdorra
*/
@XmlRootElement(name = "config")
@AuditEntry(labels = {"hg", "config"})
public class HgGlobalConfig extends RepositoryConfig {
public static final String PERMISSION = "hg";

View File

@@ -25,11 +25,13 @@
package sonia.scm.repository;
import lombok.Data;
import sonia.scm.auditlog.AuditEntry;
import javax.xml.bind.annotation.XmlRootElement;
@Data
@XmlRootElement
@AuditEntry(labels = {"hg", "config"})
public class HgRepositoryConfig {
String encoding;
}

View File

@@ -24,6 +24,8 @@
package sonia.scm.repository;
import sonia.scm.auditlog.AuditEntry;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
@@ -36,6 +38,7 @@ import javax.xml.bind.annotation.XmlTransient;
*/
@XmlRootElement(name = "config")
@XmlAccessorType(XmlAccessType.FIELD)
@AuditEntry(labels = {"svn", "config"})
public class SvnConfig extends RepositoryConfig
{

View File

@@ -25,8 +25,12 @@
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

@@ -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 --------------------------------------------------------------

View File

@@ -119,7 +119,8 @@ public class DefaultRepositoryManagerPerfTest {
repositoryDAO,
handlerSet,
Providers.of(namespaceStrategy),
repositoryPostProcessor);
repositoryPostProcessor,
Collections.emptySet());
setUpTestRepositories();

View File

@@ -48,6 +48,7 @@ import sonia.scm.NoChangesMadeException;
import sonia.scm.NotFoundException;
import sonia.scm.SCMContext;
import sonia.scm.ScmConstraintViolationException;
import sonia.scm.TempSCMContextProvider;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.api.HookContext;
import sonia.scm.repository.api.HookContextFactory;
@@ -88,9 +89,6 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import sonia.scm.TempSCMContextProvider;
//~--- JDK imports ------------------------------------------------------------
/**
@@ -561,7 +559,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
when(namespaceStrategy.createNamespace(Mockito.any(Repository.class))).thenAnswer(invocation -> mockedNamespace);
return new DefaultRepositoryManager(contextProvider,
keyGenerator, repositoryDAO, handlerSet, Providers.of(namespaceStrategy), postProcessor);
keyGenerator, repositoryDAO, handlerSet, Providers.of(namespaceStrategy), postProcessor, emptySet());
}
private RepositoryDAO createRepositoryDaoMock() {

View File

@@ -48,6 +48,7 @@ import java.util.Comparator;
import java.util.List;
import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -76,7 +77,6 @@ class DefaultRepositoryRoleManagerTest {
@Mock
private RepositoryPermissionProvider permissionProvider;
@InjectMocks
private DefaultRepositoryRoleManager manager;
@BeforeEach
@@ -103,6 +103,11 @@ class DefaultRepositoryRoleManagerTest {
when(permissionProvider.availableRoles()).thenReturn(asList(CUSTOM_ROLE, SYSTEM_ROLE));
}
@BeforeEach
void initManager() {
manager = new DefaultRepositoryRoleManager(dao, permissionProvider, emptySet());
}
@AfterEach
void cleanupContext() {
ThreadContext.unbindSubject();

View File

@@ -39,6 +39,7 @@ import sonia.scm.store.JAXBConfigurationStoreFactory;
import java.nio.file.Path;
import java.util.Optional;
import static java.util.Collections.emptySet;
import static org.mockito.Mockito.when;
import static sonia.scm.update.repository.MigrationStrategy.INLINE;
@@ -53,7 +54,7 @@ class DefaultMigrationStrategyDAOTest {
@BeforeEach
void initStore(@TempDir Path tempDir) {
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
storeFactory = new JAXBConfigurationStoreFactory(contextProvider, null, null);
storeFactory = new JAXBConfigurationStoreFactory(contextProvider, null, null, emptySet());
}
@Test

View File

@@ -36,6 +36,9 @@ import sonia.scm.NotFoundException;
import sonia.scm.store.JAXBConfigurationStoreFactory;
import sonia.scm.user.xml.XmlUserDAO;
import java.util.Collections;
import static java.util.Collections.emptySet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.anyString;
@@ -64,7 +67,7 @@ public class DefaultUserManagerTest extends UserManagerTestBase {
@Override
public UserManager createManager() {
return new DefaultUserManager(passwordService, createXmlUserDAO());
return new DefaultUserManager(passwordService, createXmlUserDAO(), emptySet());
}
@Before
@@ -78,7 +81,7 @@ public class DefaultUserManagerTest extends UserManagerTestBase {
when(passwordService.encryptPassword(anyString())).thenAnswer(invocation -> invocation.getArgument(0));
userManager = new DefaultUserManager(passwordService, userDAO);
userManager = new DefaultUserManager(passwordService, userDAO, emptySet());
}
@Test(expected = InvalidPasswordException.class)
@@ -161,6 +164,6 @@ public class DefaultUserManagerTest extends UserManagerTestBase {
}
private XmlUserDAO createXmlUserDAO() {
return new XmlUserDAO(new JAXBConfigurationStoreFactory(contextProvider, locationResolver, null));
return new XmlUserDAO(new JAXBConfigurationStoreFactory(contextProvider, locationResolver, null, emptySet()));
}
}