Files
SCM-Manager/scm-webapp/src/main/java/sonia/scm/security/DefaultSecuritySystem.java
Florian Scholdei f1b34eb502 Changeover to MIT license (#1066)
* prepare license-maven-plugin for license migration
* added license mapping for tsx files and added some more excludes
* Changeover to MIT license
* Fix build problems
* Delete old remaining licenses
* Add more exclude path for license checker
* Rename included netbeans license, add exclude .m2/repository/
* Specify .m2 exclude because not only repository/, also wrapper/ must match
* Add .cache/ exclude for license check
* Modify formatting of license in java classes to comply with convention and IDE
* Add IntelliJ documentation for license configuration
* Update CHANGELOG.md
* Exclude tmp/workspace/ dir for license check
* Edit README.md

Co-authored-by: Sebastian Sdorra <sebastian.sdorra@cloudogu.com>
2020-03-23 15:35:58 +01:00

390 lines
10 KiB
Java

/*
* 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.security;
//~--- non-JDK imports --------------------------------------------------------
import com.github.legman.Subscribe;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.HandlerEventType;
import sonia.scm.event.ScmEventBus;
import sonia.scm.group.GroupEvent;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.store.ConfigurationEntryStore;
import sonia.scm.store.ConfigurationEntryStoreFactory;
import sonia.scm.user.UserEvent;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map.Entry;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static java.util.Objects.isNull;
//~--- JDK imports ------------------------------------------------------------
/**
* TODO add events
*
* @author Sebastian Sdorra
* @since 1.31
*/
@Singleton
public class DefaultSecuritySystem implements SecuritySystem
{
/** Field description */
private static final String NAME = "security";
/** Field description */
private static final String PERMISSION_DESCRIPTOR =
"META-INF/scm/permissions.xml";
/**
* the logger for DefaultSecuritySystem
*/
private static final Logger logger =
LoggerFactory.getLogger(DefaultSecuritySystem.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param storeFactory
*/
@Inject
@SuppressWarnings("unchecked")
public DefaultSecuritySystem(ConfigurationEntryStoreFactory storeFactory, PluginLoader pluginLoader)
{
store = storeFactory
.withType(AssignedPermission.class)
.withName(NAME)
.build();
this.availablePermissions = readAvailablePermissions(pluginLoader);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param permission
*
* @return
*/
@Override
public void addPermission(AssignedPermission permission)
{
assertHasPermission();
validatePermission(permission);
String id = store.put(permission);
StoredAssignedPermission sap = new StoredAssignedPermission(id, permission);
//J-
ScmEventBus.getInstance().post(
new AssignedPermissionEvent(HandlerEventType.CREATE, permission)
);
//J+
}
/**
* Method description
*
*
* @param permission
*/
@Override
public void deletePermission(AssignedPermission permission)
{
assertHasPermission();
boolean deleted = deletePermissions(sap -> Objects.equal(sap.getName(), permission.getName())
&& Objects.equal(sap.isGroupPermission(), permission.isGroupPermission())
&& Objects.equal(sap.getPermission(), permission.getPermission()));
if (deleted) {
ScmEventBus.getInstance().post(
new AssignedPermissionEvent(HandlerEventType.DELETE, permission)
);
}
}
/**
* Method description
*
*
* @param event
*/
@Subscribe
public void handleEvent(final UserEvent event)
{
if (event.getEventType() == HandlerEventType.DELETE)
{
deletePermissions(p -> !p.isGroupPermission()
&& event.getItem().getName().equals(p.getName()));
}
}
/**
* Method description
*
*
* @param event
*/
@Subscribe
public void handleEvent(final GroupEvent event)
{
if (event.getEventType() == HandlerEventType.DELETE)
{
deletePermissions(p -> p.isGroupPermission()
&& event.getItem().getName().equals(p.getName()));
}
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public Collection<PermissionDescriptor> getAvailablePermissions()
{
assertHasPermission();
return availablePermissions;
}
/**
* Method description
*
*
* @param predicate
*
* @return
*/
@Override
public Collection<AssignedPermission> getPermissions(Predicate<AssignedPermission> predicate)
{
Builder<AssignedPermission> permissions = ImmutableSet.builder();
for (Entry<String, AssignedPermission> e : store.getAll().entrySet())
{
if ((predicate == null) || predicate.test(e.getValue()))
{
permissions.add(new StoredAssignedPermission(e.getKey(), e.getValue()));
}
}
return permissions.build();
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*/
private void assertHasPermission()
{
PermissionPermissions.assign().check();
}
/**
* Method description
*
*
* @param predicate
*/
private boolean deletePermissions(Predicate<AssignedPermission> predicate)
{
List<Entry<String, AssignedPermission>> toRemove =
store.getAll()
.entrySet()
.stream()
.filter(e -> (predicate == null) || predicate.test(e.getValue())).collect(Collectors.toList());
toRemove.forEach(e -> store.remove(e.getKey()));
return !toRemove.isEmpty();
}
/**
* Method description
*
*
* @param context
* @param descriptorUrl
*
* @return
*/
@SuppressWarnings("unchecked")
private static List<PermissionDescriptor> parsePermissionDescriptor(
JAXBContext context, URL descriptorUrl)
{
List<PermissionDescriptor> descriptors = Collections.EMPTY_LIST;
try
{
PermissionDescriptors descriptorWrapper =
(PermissionDescriptors) context.createUnmarshaller().unmarshal(
descriptorUrl);
descriptors = descriptorWrapper.getPermissions();
logger.debug("found {} permissions at {}", descriptors.size(),
descriptorUrl);
logger.trace("permissions from {}: {}", descriptorUrl, descriptors);
}
catch (JAXBException ex)
{
logger.error("could not parse permission descriptor", ex);
}
return descriptors;
}
/**
* Method description
*
* @param pluginLoader
*/
private static ImmutableSet<PermissionDescriptor> readAvailablePermissions(PluginLoader pluginLoader)
{
ImmutableSet.Builder<PermissionDescriptor> builder = ImmutableSet.builder();
try
{
JAXBContext context =
JAXBContext.newInstance(PermissionDescriptors.class);
// Querying permissions from uberClassLoader returns also the permissions from plugin
Enumeration<URL> descirptorEnum =
pluginLoader.getUberClassLoader().getResources(PERMISSION_DESCRIPTOR);
while (descirptorEnum.hasMoreElements())
{
URL descriptorUrl = descirptorEnum.nextElement();
logger.debug("read permission descriptor from {}", descriptorUrl);
builder.addAll(parsePermissionDescriptor(context, descriptorUrl));
}
}
catch (IOException ex)
{
logger.error("could not read permission descriptors", ex);
}
catch (JAXBException ex)
{
logger.error(
"could not create jaxb context to read permission descriptors", ex);
}
return builder.build();
}
/**
* Method description
*
*
* @param perm
*/
private void validatePermission(AssignedPermission perm)
{
Preconditions.checkArgument(!Strings.isNullOrEmpty(perm.getName()),
"name is required");
Preconditions.checkArgument(!isNull(perm.getPermission()),
"permission is required");
}
//~--- inner classes --------------------------------------------------------
/**
* Descriptor for permissions.
*/
@XmlRootElement(name = "permissions")
@XmlAccessorType(XmlAccessType.FIELD)
private static class PermissionDescriptors
{
//~--- get methods --------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@SuppressWarnings("unchecked")
public List<PermissionDescriptor> getPermissions()
{
if (permissions == null)
{
permissions = Collections.EMPTY_LIST;
}
return permissions;
}
//~--- fields -------------------------------------------------------------
/** Field description */
@XmlElement(name = "permission")
private List<PermissionDescriptor> permissions;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final ConfigurationEntryStore<AssignedPermission> store;
/** Field description */
private final ImmutableSet<PermissionDescriptor> availablePermissions;
}