mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-15 17:56:17 +01:00
Fix audit log issues:
- Use store name as label for repository related changes if no explicit labels are set. - Introduce 'ignore' flag - Fix missing call to store - Create audit logs for permissions - Set flex attributes for input field to use full available space Co-authored-by: René Pfeuffer <rene.pfeuffer@cloudogu.com> Co-authored-by: Konstantin Schaper <konstantin.schaper@cloudogu.com>
This commit is contained in:
committed by
SCM-Manager
parent
1d0baf48e2
commit
b511789620
@@ -25,14 +25,17 @@
|
|||||||
package sonia.scm.auditlog;
|
package sonia.scm.auditlog;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Inherited;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Inherited
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target({ElementType.TYPE, ElementType.LOCAL_VARIABLE, ElementType.FIELD, ElementType.PACKAGE, ElementType.METHOD})
|
@Target({ElementType.TYPE})
|
||||||
public @interface AuditEntry {
|
public @interface AuditEntry {
|
||||||
String[] labels() default {};
|
String[] labels() default {};
|
||||||
String[] maskedFields() default {};
|
String[] maskedFields() default {};
|
||||||
String[] ignoredFields() default {};
|
String[] ignoredFields() default {};
|
||||||
|
boolean ignore() default false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,11 @@ import sonia.scm.repository.RepositoryDAO;
|
|||||||
import sonia.scm.store.ConfigurationStore;
|
import sonia.scm.store.ConfigurationStore;
|
||||||
import sonia.scm.store.StoreDecoratorFactory;
|
import sonia.scm.store.StoreDecoratorFactory;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptySet;
|
||||||
|
|
||||||
public class AuditLogConfigurationStoreDecorator<T> implements ConfigurationStore<T> {
|
public class AuditLogConfigurationStoreDecorator<T> implements ConfigurationStore<T> {
|
||||||
|
|
||||||
private final Set<Auditor> auditors;
|
private final Set<Auditor> auditors;
|
||||||
@@ -50,13 +53,40 @@ public class AuditLogConfigurationStoreDecorator<T> implements ConfigurationStor
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void set(T object) {
|
public void set(T object) {
|
||||||
String repositoryId = context.getStoreParameters().getRepositoryId();
|
if (!shouldBeIgnored(object)) {
|
||||||
if (!Strings.isNullOrEmpty(repositoryId)) {
|
auditors.forEach(s -> s.createEntry(createEntryCreationContext(object)));
|
||||||
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);
|
delegate.set(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EntryCreationContext<T> createEntryCreationContext(T object) {
|
||||||
|
String repositoryId = context.getStoreParameters().getRepositoryId();
|
||||||
|
if (!Strings.isNullOrEmpty(repositoryId)) {
|
||||||
|
String name = repositoryDAO.get(repositoryId).getNamespaceAndName().toString();
|
||||||
|
return new EntryCreationContext<>(object, get(), name, getRepositoryLabels(object));
|
||||||
|
} else {
|
||||||
|
return new EntryCreationContext<>(object, get(), "", shouldUseStoreNameAsLabel(object) ? Set.of(context.getStoreParameters().getName()) : emptySet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldBeIgnored(T object) {
|
||||||
|
return getAnnotation(object).map(AuditEntry::ignore).orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> getRepositoryLabels(T object) {
|
||||||
|
Set<String> labels = new java.util.HashSet<>();
|
||||||
|
labels.add("repository");
|
||||||
|
if (shouldUseStoreNameAsLabel(object)) {
|
||||||
|
labels.add(context.getStoreParameters().getName());
|
||||||
|
}
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldUseStoreNameAsLabel(T object) {
|
||||||
|
return getAnnotation(object).map(annotation -> annotation.labels().length == 0).orElse(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<AuditEntry> getAnnotation(T object) {
|
||||||
|
return Optional.ofNullable(object.getClass().getAnnotation(AuditEntry.class));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ package sonia.scm.security;
|
|||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import sonia.scm.auditlog.AuditEntry;
|
import sonia.scm.auditlog.AuditEntry;
|
||||||
|
import sonia.scm.auditlog.AuditLogEntity;
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
@@ -47,7 +48,7 @@ import java.io.Serializable;
|
|||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
@XmlRootElement(name = "assigned-permission")
|
@XmlRootElement(name = "assigned-permission")
|
||||||
@AuditEntry(labels = "permission")
|
@AuditEntry(labels = "permission")
|
||||||
public class AssignedPermission implements PermissionObject, Serializable
|
public class AssignedPermission implements PermissionObject, Serializable, AuditLogEntity
|
||||||
{
|
{
|
||||||
|
|
||||||
/** serial version uid */
|
/** serial version uid */
|
||||||
@@ -207,4 +208,9 @@ public class AssignedPermission implements PermissionObject, Serializable
|
|||||||
|
|
||||||
/** string representation of the permission */
|
/** string representation of the permission */
|
||||||
private PermissionDescriptor permission;
|
private PermissionDescriptor permission;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEntityName() {
|
||||||
|
return getName();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,231 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryDAO;
|
||||||
|
import sonia.scm.store.ConfigurationStore;
|
||||||
|
import sonia.scm.store.StoreDecoratorFactory;
|
||||||
|
import sonia.scm.store.TypedStoreParameters;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class AuditLogConfigurationStoreDecoratorTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Auditor auditor;
|
||||||
|
@Mock
|
||||||
|
private RepositoryDAO repositoryDAO;
|
||||||
|
@Mock
|
||||||
|
private ConfigurationStore<Object> delegate;
|
||||||
|
@Mock
|
||||||
|
private StoreDecoratorFactory.Context storeContext;
|
||||||
|
@Mock
|
||||||
|
private TypedStoreParameters parameters;
|
||||||
|
|
||||||
|
private AuditLogConfigurationStoreDecorator<Object> decorator;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpDecorator() {
|
||||||
|
decorator = new AuditLogConfigurationStoreDecorator<>(Set.of(auditor), repositoryDAO, delegate, storeContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class WithAuditableEntries {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpStoreContext() {
|
||||||
|
when(storeContext.getStoreParameters()).thenReturn(parameters);
|
||||||
|
lenient().when(parameters.getName()).thenReturn("hog");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCallAuditorForSimpleEntry() {
|
||||||
|
Object entry = new SimpleEntry();
|
||||||
|
|
||||||
|
decorator.set(entry);
|
||||||
|
|
||||||
|
verify(auditor).createEntry(argThat(
|
||||||
|
context -> {
|
||||||
|
assertThat(context.getEntity()).isEmpty();
|
||||||
|
assertThat(context.getAdditionalLabels()).contains("hog");
|
||||||
|
assertThat(context.getObject()).isSameAs(entry);
|
||||||
|
assertThat(context.getOldObject()).isNull();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCallAuditorForAdditionalLabelEntry() {
|
||||||
|
Object entry = new ExtraLabelEntry();
|
||||||
|
|
||||||
|
decorator.set(entry);
|
||||||
|
|
||||||
|
verify(auditor).createEntry(argThat(
|
||||||
|
context -> {
|
||||||
|
assertThat(context.getEntity()).isEmpty();
|
||||||
|
assertThat(context.getAdditionalLabels()).isEmpty();
|
||||||
|
assertThat(context.getObject()).isSameAs(entry);
|
||||||
|
assertThat(context.getOldObject()).isNull();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCallDelegateForSimpleEntry() {
|
||||||
|
Object entry = new SimpleEntry();
|
||||||
|
|
||||||
|
decorator.set(entry);
|
||||||
|
|
||||||
|
verify(delegate).set(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class ForRepositoryStore {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void mockRepositoryContext() {
|
||||||
|
when(parameters.getRepositoryId()).thenReturn("42");
|
||||||
|
when(repositoryDAO.get("42")).thenReturn(new Repository("42", "git", "hitchhiker", "hog"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCallAuditorForSimpleEntry() {
|
||||||
|
Object entry = new SimpleEntry();
|
||||||
|
|
||||||
|
decorator.set(entry);
|
||||||
|
|
||||||
|
verify(auditor).createEntry(argThat(
|
||||||
|
context -> {
|
||||||
|
assertThat(context.getEntity()).isEqualTo("hitchhiker/hog");
|
||||||
|
assertThat(context.getAdditionalLabels()).contains("hog");
|
||||||
|
assertThat(context.getObject()).isSameAs(entry);
|
||||||
|
assertThat(context.getOldObject()).isNull();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCallAuditorForAdditionalLabelEntry() {
|
||||||
|
Object entry = new ExtraLabelEntry();
|
||||||
|
|
||||||
|
decorator.set(entry);
|
||||||
|
|
||||||
|
verify(auditor).createEntry(argThat(
|
||||||
|
context -> {
|
||||||
|
assertThat(context.getEntity()).isEqualTo("hitchhiker/hog");
|
||||||
|
assertThat(context.getAdditionalLabels()).contains("repository");
|
||||||
|
assertThat(context.getObject()).isSameAs(entry);
|
||||||
|
assertThat(context.getOldObject()).isNull();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUseOldObjectFromStore() {
|
||||||
|
Object oldObject = new Object();
|
||||||
|
when(delegate.get()).thenReturn(oldObject);
|
||||||
|
|
||||||
|
Object entry = new SimpleEntry();
|
||||||
|
|
||||||
|
decorator.set(entry);
|
||||||
|
|
||||||
|
verify(auditor).createEntry(argThat(
|
||||||
|
context -> {
|
||||||
|
assertThat(context.getOldObject()).isSameAs(oldObject);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUseOldObjectFromStore() {
|
||||||
|
Object oldObject = new Object();
|
||||||
|
when(delegate.get()).thenReturn(oldObject);
|
||||||
|
|
||||||
|
Object entry = new SimpleEntry();
|
||||||
|
|
||||||
|
decorator.set(entry);
|
||||||
|
|
||||||
|
verify(auditor).createEntry(argThat(
|
||||||
|
context -> {
|
||||||
|
assertThat(context.getOldObject()).isSameAs(oldObject);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotCallAuditorForIgnoredEntry() {
|
||||||
|
Object entry = new IgnoredEntry();
|
||||||
|
|
||||||
|
decorator.set(entry);
|
||||||
|
|
||||||
|
verify(auditor, never()).createEntry(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCallDelegateForIgnoredEntry() {
|
||||||
|
Object entry = new IgnoredEntry();
|
||||||
|
|
||||||
|
decorator.set(entry);
|
||||||
|
|
||||||
|
verify(delegate).set(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AuditEntry
|
||||||
|
private static class SimpleEntry {
|
||||||
|
}
|
||||||
|
|
||||||
|
@AuditEntry(labels = "permission")
|
||||||
|
private static class ExtraLabelEntry {
|
||||||
|
}
|
||||||
|
|
||||||
|
@AuditEntry(ignore = true)
|
||||||
|
private static class IgnoredEntry {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
package sonia.scm.group.xml;
|
package sonia.scm.group.xml;
|
||||||
|
|
||||||
|
import sonia.scm.auditlog.AuditEntry;
|
||||||
import sonia.scm.group.Group;
|
import sonia.scm.group.Group;
|
||||||
import sonia.scm.xml.XmlDatabase;
|
import sonia.scm.xml.XmlDatabase;
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ import java.util.TreeMap;
|
|||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
|
@AuditEntry(ignore = true)
|
||||||
@XmlRootElement(name = "group-db")
|
@XmlRootElement(name = "group-db")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class XmlGroupDatabase implements XmlDatabase<Group>
|
public class XmlGroupDatabase implements XmlDatabase<Group>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.xml;
|
package sonia.scm.repository.xml;
|
||||||
|
|
||||||
|
import sonia.scm.auditlog.AuditEntry;
|
||||||
import sonia.scm.repository.RepositoryRole;
|
import sonia.scm.repository.RepositoryRole;
|
||||||
import sonia.scm.xml.XmlDatabase;
|
import sonia.scm.xml.XmlDatabase;
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ import java.util.Collection;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
@AuditEntry(ignore = true)
|
||||||
@XmlRootElement(name = "user-db")
|
@XmlRootElement(name = "user-db")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class XmlRepositoryRoleDatabase implements XmlDatabase<RepositoryRole> {
|
public class XmlRepositoryRoleDatabase implements XmlDatabase<RepositoryRole> {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
package sonia.scm.user.xml;
|
package sonia.scm.user.xml;
|
||||||
|
|
||||||
|
import sonia.scm.auditlog.AuditEntry;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.xml.XmlDatabase;
|
import sonia.scm.xml.XmlDatabase;
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ import java.util.TreeMap;
|
|||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
|
@AuditEntry(ignore = true)
|
||||||
@XmlRootElement(name = "user-db")
|
@XmlRootElement(name = "user-db")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class XmlUserDatabase implements XmlDatabase<User>
|
public class XmlUserDatabase implements XmlDatabase<User>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ type Props = {
|
|||||||
const Select = React.forwardRef<HTMLSelectElement, Props>(
|
const Select = React.forwardRef<HTMLSelectElement, Props>(
|
||||||
({ variant, children, className, options, testId, ...props }, ref) => (
|
({ variant, children, className, options, testId, ...props }, ref) => (
|
||||||
<div className={classNames("select", { "is-multiple": props.multiple }, createVariantClass(variant), className)}>
|
<div className={classNames("select", { "is-multiple": props.multiple }, createVariantClass(variant), className)}>
|
||||||
<select ref={ref} {...props} {...createAttributesForTesting(testId)}>
|
<select ref={ref} {...props} {...createAttributesForTesting(testId)} className={className}>
|
||||||
{options
|
{options
|
||||||
? options.map((option) => (
|
? options.map((option) => (
|
||||||
<option {...option} key={option.value as Key}>
|
<option {...option} key={option.value as Key}>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
package sonia.scm.group;
|
package sonia.scm.group;
|
||||||
|
|
||||||
|
import sonia.scm.auditlog.AuditEntry;
|
||||||
import sonia.scm.xml.XmlMapMultiStringAdapter;
|
import sonia.scm.xml.XmlMapMultiStringAdapter;
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
@@ -36,6 +37,7 @@ import java.util.Set;
|
|||||||
|
|
||||||
import static java.util.Collections.emptySet;
|
import static java.util.Collections.emptySet;
|
||||||
|
|
||||||
|
@AuditEntry(ignore = true)
|
||||||
@XmlRootElement(name = "user-group-cache")
|
@XmlRootElement(name = "user-group-cache")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
class UserGroupCache {
|
class UserGroupCache {
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ import com.google.inject.Singleton;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.HandlerEventType;
|
import sonia.scm.HandlerEventType;
|
||||||
|
import sonia.scm.auditlog.AuditEntry;
|
||||||
|
import sonia.scm.auditlog.Auditor;
|
||||||
|
import sonia.scm.auditlog.EntryCreationContext;
|
||||||
import sonia.scm.event.ScmEventBus;
|
import sonia.scm.event.ScmEventBus;
|
||||||
import sonia.scm.group.GroupEvent;
|
import sonia.scm.group.GroupEvent;
|
||||||
import sonia.scm.plugin.PluginLoader;
|
import sonia.scm.plugin.PluginLoader;
|
||||||
@@ -52,19 +55,18 @@ import javax.xml.bind.annotation.XmlRootElement;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Objects.isNull;
|
import static java.util.Objects.isNull;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO add events
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
* @since 1.31
|
* @since 1.31
|
||||||
*/
|
*/
|
||||||
@@ -76,19 +78,23 @@ public class DefaultSecuritySystem implements SecuritySystem {
|
|||||||
private static final String PERMISSION_DESCRIPTOR =
|
private static final String PERMISSION_DESCRIPTOR =
|
||||||
"META-INF/scm/permissions.xml";
|
"META-INF/scm/permissions.xml";
|
||||||
|
|
||||||
/**
|
|
||||||
* the logger for DefaultSecuritySystem
|
|
||||||
*/
|
|
||||||
private static final Logger logger =
|
private static final Logger logger =
|
||||||
LoggerFactory.getLogger(DefaultSecuritySystem.class);
|
LoggerFactory.getLogger(DefaultSecuritySystem.class);
|
||||||
|
|
||||||
|
private final ConfigurationEntryStore<AssignedPermission> store;
|
||||||
|
|
||||||
|
private final ImmutableSet<PermissionDescriptor> availablePermissions;
|
||||||
|
|
||||||
|
private final Set<Auditor> auditors;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public DefaultSecuritySystem(ConfigurationEntryStoreFactory storeFactory, PluginLoader pluginLoader) {
|
public DefaultSecuritySystem(ConfigurationEntryStoreFactory storeFactory, PluginLoader pluginLoader, Set<Auditor> auditors) {
|
||||||
store = storeFactory
|
store = storeFactory
|
||||||
.withType(AssignedPermission.class)
|
.withType(AssignedPermission.class)
|
||||||
.withName(NAME)
|
.withName(NAME)
|
||||||
.build();
|
.build();
|
||||||
this.availablePermissions = readAvailablePermissions(pluginLoader);
|
this.availablePermissions = readAvailablePermissions(pluginLoader);
|
||||||
|
this.auditors = auditors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -96,9 +102,9 @@ public class DefaultSecuritySystem implements SecuritySystem {
|
|||||||
assertHasPermission();
|
assertHasPermission();
|
||||||
validatePermission(permission);
|
validatePermission(permission);
|
||||||
|
|
||||||
String id = store.put(permission);
|
callAuditors(null, permission);
|
||||||
|
|
||||||
StoredAssignedPermission sap = new StoredAssignedPermission(id, permission);
|
store.put(permission);
|
||||||
|
|
||||||
//J-
|
//J-
|
||||||
ScmEventBus.getInstance().post(
|
ScmEventBus.getInstance().post(
|
||||||
@@ -114,6 +120,7 @@ public class DefaultSecuritySystem implements SecuritySystem {
|
|||||||
&& Objects.equal(sap.isGroupPermission(), permission.isGroupPermission())
|
&& Objects.equal(sap.isGroupPermission(), permission.isGroupPermission())
|
||||||
&& Objects.equal(sap.getPermission(), permission.getPermission()));
|
&& Objects.equal(sap.getPermission(), permission.getPermission()));
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
|
callAuditors(permission, null);
|
||||||
ScmEventBus.getInstance().post(
|
ScmEventBus.getInstance().post(
|
||||||
new AssignedPermissionEvent(HandlerEventType.DELETE, permission)
|
new AssignedPermissionEvent(HandlerEventType.DELETE, permission)
|
||||||
);
|
);
|
||||||
@@ -170,10 +177,9 @@ public class DefaultSecuritySystem implements SecuritySystem {
|
|||||||
return !toRemove.isEmpty();
|
return !toRemove.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static List<PermissionDescriptor> parsePermissionDescriptor(
|
private static List<PermissionDescriptor> parsePermissionDescriptor(
|
||||||
JAXBContext context, URL descriptorUrl) {
|
JAXBContext context, URL descriptorUrl) {
|
||||||
List<PermissionDescriptor> descriptors = Collections.EMPTY_LIST;
|
List<PermissionDescriptor> descriptors = emptyList();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
PermissionDescriptors descriptorWrapper =
|
PermissionDescriptors descriptorWrapper =
|
||||||
@@ -227,6 +233,15 @@ public class DefaultSecuritySystem implements SecuritySystem {
|
|||||||
"permission is required");
|
"permission is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void callAuditors(AssignedPermission notModified, AssignedPermission newObject) {
|
||||||
|
AssignedPermission nonNullPermission = newObject == null ? notModified : newObject;
|
||||||
|
if (nonNullPermission.getClass().isAnnotationPresent(AuditEntry.class)) {
|
||||||
|
String label = nonNullPermission.isGroupPermission() ? "group" : "user";
|
||||||
|
EntryCreationContext<AssignedPermission> context = new EntryCreationContext<>(newObject, notModified, nonNullPermission.getEntityName(), Set.of(label));
|
||||||
|
auditors.forEach(s -> s.createEntry(context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Descriptor for permissions.
|
* Descriptor for permissions.
|
||||||
*/
|
*/
|
||||||
@@ -234,10 +249,9 @@ public class DefaultSecuritySystem implements SecuritySystem {
|
|||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
private static class PermissionDescriptors {
|
private static class PermissionDescriptors {
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public List<PermissionDescriptor> getPermissions() {
|
public List<PermissionDescriptor> getPermissions() {
|
||||||
if (permissions == null) {
|
if (permissions == null) {
|
||||||
permissions = Collections.EMPTY_LIST;
|
permissions = emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return permissions;
|
return permissions;
|
||||||
@@ -246,8 +260,4 @@ public class DefaultSecuritySystem implements SecuritySystem {
|
|||||||
@XmlElement(name = "permission")
|
@XmlElement(name = "permission")
|
||||||
private List<PermissionDescriptor> permissions;
|
private List<PermissionDescriptor> permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ConfigurationEntryStore<AssignedPermission> store;
|
|
||||||
|
|
||||||
private final ImmutableSet<PermissionDescriptor> availablePermissions;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ public class PermissionAssigner {
|
|||||||
permissions.stream()
|
permissions.stream()
|
||||||
.filter(permissionExists(availablePermissions, existingPermissions))
|
.filter(permissionExists(availablePermissions, existingPermissions))
|
||||||
.map(p -> new AssignedPermission(id, groupPermission, p))
|
.map(p -> new AssignedPermission(id, groupPermission, p))
|
||||||
.filter(p -> !existingPermissions.contains(p))
|
.filter(p -> existingPermissions.stream().map(AssignedPermission::getPermission).noneMatch(p2 -> p.getPermission().equals(p2)))
|
||||||
.forEach(securitySystem::addPermission);
|
.forEach(securitySystem::addPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,21 +30,26 @@ import org.apache.shiro.mgt.DefaultSecurityManager;
|
|||||||
import org.apache.shiro.realm.SimpleAccountRealm;
|
import org.apache.shiro.realm.SimpleAccountRealm;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
import sonia.scm.AbstractTestBase;
|
import sonia.scm.AbstractTestBase;
|
||||||
|
import sonia.scm.auditlog.Auditor;
|
||||||
import sonia.scm.plugin.PluginLoader;
|
import sonia.scm.plugin.PluginLoader;
|
||||||
import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
|
import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
|
||||||
import sonia.scm.util.ClassLoaders;
|
import sonia.scm.util.ClassLoaders;
|
||||||
import sonia.scm.util.MockUtil;
|
import sonia.scm.util.MockUtil;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,14 +61,11 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
|
|||||||
|
|
||||||
private JAXBConfigurationEntryStoreFactory jaxbConfigurationEntryStoreFactory;
|
private JAXBConfigurationEntryStoreFactory jaxbConfigurationEntryStoreFactory;
|
||||||
private PluginLoader pluginLoader;
|
private PluginLoader pluginLoader;
|
||||||
@InjectMocks
|
@Mock
|
||||||
|
private Auditor auditor;
|
||||||
|
|
||||||
private DefaultSecuritySystem securitySystem;
|
private DefaultSecuritySystem securitySystem;
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Before
|
@Before
|
||||||
public void createSecuritySystem()
|
public void createSecuritySystem()
|
||||||
{
|
{
|
||||||
@@ -73,12 +75,9 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
|
|||||||
when(pluginLoader.getUberClassLoader()).thenReturn(ClassLoaders.getContextClassLoader(DefaultSecuritySystem.class));
|
when(pluginLoader.getUberClassLoader()).thenReturn(ClassLoaders.getContextClassLoader(DefaultSecuritySystem.class));
|
||||||
|
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
securitySystem = new DefaultSecuritySystem(jaxbConfigurationEntryStoreFactory, pluginLoader, Set.of(auditor));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddPermission()
|
public void testAddPermission()
|
||||||
{
|
{
|
||||||
@@ -91,10 +90,6 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
|
|||||||
assertEquals(false, sap.isGroupPermission());
|
assertEquals(false, sap.isGroupPermission());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testAvailablePermissions()
|
public void testAvailablePermissions()
|
||||||
{
|
{
|
||||||
@@ -106,10 +101,6 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
|
|||||||
assertThat(list).isNotEmpty();
|
assertThat(list).isNotEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeletePermission()
|
public void testDeletePermission()
|
||||||
{
|
{
|
||||||
@@ -123,10 +114,6 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
|
|||||||
assertThat(securitySystem.getPermissions(p -> p.getName().equals("trillian"))).isEmpty();
|
assertThat(securitySystem.getPermissions(p -> p.getName().equals("trillian"))).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetAllPermissions()
|
public void testGetAllPermissions()
|
||||||
{
|
{
|
||||||
@@ -145,10 +132,6 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
|
|||||||
assertThat(all).contains(trillian, dent, marvin);
|
assertThat(all).contains(trillian, dent, marvin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetPermission()
|
public void testGetPermission()
|
||||||
{
|
{
|
||||||
@@ -162,10 +145,6 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
|
|||||||
assertThat(other).containsExactly(sap);
|
assertThat(other).containsExactly(sap);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetPermissionsWithPredicate()
|
public void testGetPermissionsWithPredicate()
|
||||||
{
|
{
|
||||||
@@ -186,10 +165,6 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
|
|||||||
.contains(trillian, dent);
|
.contains(trillian, dent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Test(expected = UnauthorizedException.class)
|
@Test(expected = UnauthorizedException.class)
|
||||||
public void testUnauthorizedAddPermission()
|
public void testUnauthorizedAddPermission()
|
||||||
{
|
{
|
||||||
@@ -197,10 +172,6 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
|
|||||||
createPermission("trillian", false, "repository:*:READ");
|
createPermission("trillian", false, "repository:*:READ");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Test(expected = UnauthorizedException.class)
|
@Test(expected = UnauthorizedException.class)
|
||||||
public void testUnauthorizedDeletePermission()
|
public void testUnauthorizedDeletePermission()
|
||||||
{
|
{
|
||||||
@@ -213,16 +184,94 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
|
|||||||
securitySystem.deletePermission(sap);
|
securitySystem.deletePermission(sap);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Test
|
||||||
* Method description
|
public void shouldCallAuditorForNewUserPermission()
|
||||||
*
|
{
|
||||||
*
|
setAdminSubject();
|
||||||
* @param name
|
|
||||||
* @param groupPermission
|
createPermission("trillian", false, "repository:*:READ");
|
||||||
* @param value
|
|
||||||
*
|
verify(auditor).createEntry(argThat(
|
||||||
* @return
|
context -> {
|
||||||
*/
|
assertThat(context.getEntity()).isEqualTo("trillian");
|
||||||
|
assertThat(context.getAdditionalLabels()).contains("user");
|
||||||
|
assertThat(context.getOldObject()).isNull();
|
||||||
|
assertThat(context.getObject())
|
||||||
|
.extracting("permission")
|
||||||
|
.extracting("value")
|
||||||
|
.isEqualTo("repository:*:READ");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCallAuditorForNewGroupPermission()
|
||||||
|
{
|
||||||
|
setAdminSubject();
|
||||||
|
|
||||||
|
createPermission("trillian", true, "repository:*:READ");
|
||||||
|
|
||||||
|
verify(auditor).createEntry(argThat(
|
||||||
|
context -> {
|
||||||
|
assertThat(context.getEntity()).isEqualTo("trillian");
|
||||||
|
assertThat(context.getAdditionalLabels()).contains("group");
|
||||||
|
assertThat(context.getOldObject()).isNull();
|
||||||
|
assertThat(context.getObject())
|
||||||
|
.extracting("permission")
|
||||||
|
.extracting("value")
|
||||||
|
.isEqualTo("repository:*:READ");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCallAuditorForRemovedUserPermission()
|
||||||
|
{
|
||||||
|
setAdminSubject();
|
||||||
|
|
||||||
|
createPermission("trillian", false, "repository:*:READ");
|
||||||
|
reset(auditor);
|
||||||
|
securitySystem.deletePermission(new AssignedPermission("trillian", false, "repository:*:READ"));
|
||||||
|
|
||||||
|
verify(auditor).createEntry(argThat(
|
||||||
|
context -> {
|
||||||
|
assertThat(context.getEntity()).isEqualTo("trillian");
|
||||||
|
assertThat(context.getAdditionalLabels()).contains("user");
|
||||||
|
assertThat(context.getObject()).isNull();
|
||||||
|
assertThat(context.getOldObject())
|
||||||
|
.extracting("permission")
|
||||||
|
.extracting("value")
|
||||||
|
.isEqualTo("repository:*:READ");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCallAuditorForRemovedGroupPermission()
|
||||||
|
{
|
||||||
|
setAdminSubject();
|
||||||
|
|
||||||
|
createPermission("trillian", true, "repository:*:READ");
|
||||||
|
reset(auditor);
|
||||||
|
securitySystem.deletePermission(new AssignedPermission("trillian", true, "repository:*:READ"));
|
||||||
|
|
||||||
|
verify(auditor).createEntry(argThat(
|
||||||
|
context -> {
|
||||||
|
assertThat(context.getEntity()).isEqualTo("trillian");
|
||||||
|
assertThat(context.getAdditionalLabels()).contains("group");
|
||||||
|
assertThat(context.getObject()).isNull();
|
||||||
|
assertThat(context.getOldObject())
|
||||||
|
.extracting("permission")
|
||||||
|
.extracting("value")
|
||||||
|
.isEqualTo("repository:*:READ");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
private AssignedPermission createPermission(String name,
|
private AssignedPermission createPermission(String name,
|
||||||
boolean groupPermission, String value)
|
boolean groupPermission, String value)
|
||||||
{
|
{
|
||||||
@@ -235,21 +284,11 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
|
|||||||
&& Objects.equal(value, permission.getPermission().getValue())).stream().findAny().orElseThrow(() -> new AssertionError("created permission not found"));
|
&& Objects.equal(value, permission.getPermission().getValue())).stream().findAny().orElseThrow(() -> new AssertionError("created permission not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private void setAdminSubject()
|
private void setAdminSubject()
|
||||||
{
|
{
|
||||||
setSubject(MockUtil.createAdminSubject());
|
setSubject(MockUtil.createAdminSubject());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private void setUserSubject()
|
private void setUserSubject()
|
||||||
{
|
{
|
||||||
org.apache.shiro.mgt.SecurityManager sm =
|
org.apache.shiro.mgt.SecurityManager sm =
|
||||||
|
|||||||
@@ -27,22 +27,28 @@ package sonia.scm.security;
|
|||||||
import com.github.sdorra.shiro.ShiroRule;
|
import com.github.sdorra.shiro.ShiroRule;
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
import org.apache.shiro.authz.UnauthorizedException;
|
import org.apache.shiro.authz.UnauthorizedException;
|
||||||
import org.assertj.core.api.Assertions;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
import sonia.scm.NotFoundException;
|
import sonia.scm.NotFoundException;
|
||||||
|
import sonia.scm.auditlog.Auditor;
|
||||||
import sonia.scm.plugin.PluginLoader;
|
import sonia.scm.plugin.PluginLoader;
|
||||||
import sonia.scm.store.InMemoryConfigurationEntryStoreFactory;
|
import sonia.scm.store.InMemoryConfigurationEntryStoreFactory;
|
||||||
import sonia.scm.util.ClassLoaders;
|
import sonia.scm.util.ClassLoaders;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini", username = "dent", password = "secret")
|
@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini", username = "dent", password = "secret")
|
||||||
@@ -57,12 +63,14 @@ public class PermissionAssignerTest {
|
|||||||
private DefaultSecuritySystem securitySystem;
|
private DefaultSecuritySystem securitySystem;
|
||||||
private PermissionAssigner permissionAssigner;
|
private PermissionAssigner permissionAssigner;
|
||||||
|
|
||||||
|
private Auditor auditor = mock(Auditor.class);
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() {
|
public void init() {
|
||||||
PluginLoader pluginLoader = mock(PluginLoader.class);
|
PluginLoader pluginLoader = mock(PluginLoader.class);
|
||||||
when(pluginLoader.getUberClassLoader()).thenReturn(ClassLoaders.getContextClassLoader(DefaultSecuritySystem.class));
|
when(pluginLoader.getUberClassLoader()).thenReturn(ClassLoaders.getContextClassLoader(DefaultSecuritySystem.class));
|
||||||
|
|
||||||
securitySystem = new DefaultSecuritySystem(new InMemoryConfigurationEntryStoreFactory(), pluginLoader) {
|
securitySystem = new DefaultSecuritySystem(new InMemoryConfigurationEntryStoreFactory(), pluginLoader, Set.of(auditor)) {
|
||||||
@Override
|
@Override
|
||||||
public Collection<PermissionDescriptor> getAvailablePermissions() {
|
public Collection<PermissionDescriptor> getAvailablePermissions() {
|
||||||
return Arrays.stream(new String[]{"perm:read:1", "perm:read:2", "perm:read:3", "perm:read:4"})
|
return Arrays.stream(new String[]{"perm:read:1", "perm:read:2", "perm:read:3", "perm:read:4"})
|
||||||
@@ -86,14 +94,14 @@ public class PermissionAssignerTest {
|
|||||||
public void shouldFindUserPermissions() {
|
public void shouldFindUserPermissions() {
|
||||||
Collection<PermissionDescriptor> permissionDescriptors = permissionAssigner.readPermissionsForUser("1");
|
Collection<PermissionDescriptor> permissionDescriptors = permissionAssigner.readPermissionsForUser("1");
|
||||||
|
|
||||||
Assertions.assertThat(permissionDescriptors).hasSize(2);
|
assertThat(permissionDescriptors).hasSize(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldFindGroupPermissions() {
|
public void shouldFindGroupPermissions() {
|
||||||
Collection<PermissionDescriptor> permissionDescriptors = permissionAssigner.readPermissionsForUser("1");
|
Collection<PermissionDescriptor> permissionDescriptors = permissionAssigner.readPermissionsForUser("1");
|
||||||
|
|
||||||
Assertions.assertThat(permissionDescriptors).hasSize(2);
|
assertThat(permissionDescriptors).hasSize(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -110,7 +118,7 @@ public class PermissionAssignerTest {
|
|||||||
|
|
||||||
Collection<PermissionDescriptor> permissionDescriptors = permissionAssigner.readPermissionsForUser("2");
|
Collection<PermissionDescriptor> permissionDescriptors = permissionAssigner.readPermissionsForUser("2");
|
||||||
|
|
||||||
Assertions.assertThat(permissionDescriptors).hasSize(2);
|
assertThat(permissionDescriptors).hasSize(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -132,5 +140,47 @@ public class PermissionAssignerTest {
|
|||||||
securitySystem.addPermission(new AssignedPermission("2", "perm:read:5"));
|
securitySystem.addPermission(new AssignedPermission("2", "perm:read:5"));
|
||||||
|
|
||||||
permissionAssigner.setPermissionsForUser("2", asList(new PermissionDescriptor("perm:read:5")));
|
permissionAssigner.setPermissionsForUser("2", asList(new PermissionDescriptor("perm:read:5")));
|
||||||
|
|
||||||
|
assertThat(permissionAssigner.readPermissionsForUser("2")).hasSize(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCallAuditorForCreation() {
|
||||||
|
reset(auditor);
|
||||||
|
|
||||||
|
permissionAssigner.setPermissionsForUser("2", asList(new PermissionDescriptor("perm:read:2"), new PermissionDescriptor("perm:read:4")));
|
||||||
|
|
||||||
|
verify(auditor).createEntry(argThat(
|
||||||
|
context -> {
|
||||||
|
assertThat(context.getEntity()).isEqualTo("2");
|
||||||
|
assertThat(context.getAdditionalLabels()).contains("user");
|
||||||
|
assertThat(context.getOldObject()).isNull();
|
||||||
|
assertThat(context.getObject())
|
||||||
|
.extracting("permission")
|
||||||
|
.extracting("value")
|
||||||
|
.isEqualTo("perm:read:4");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCallAuditorForRemoval() {
|
||||||
|
reset(auditor);
|
||||||
|
|
||||||
|
permissionAssigner.setPermissionsForUser("2", emptyList());
|
||||||
|
|
||||||
|
verify(auditor).createEntry(argThat(
|
||||||
|
context -> {
|
||||||
|
assertThat(context.getEntity()).isEqualTo("2");
|
||||||
|
assertThat(context.getAdditionalLabels()).contains("user");
|
||||||
|
assertThat(context.getObject()).isNull();
|
||||||
|
assertThat(context.getOldObject())
|
||||||
|
.extracting("permission")
|
||||||
|
.extracting("value")
|
||||||
|
.isEqualTo("perm:read:2");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user