package sonia.scm.security; import com.google.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.plugin.PluginLoader; 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.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import static java.util.Collections.unmodifiableCollection; public class RepositoryPermissionProvider { private static final Logger logger = LoggerFactory.getLogger(RepositoryPermissionProvider.class); private static final String REPOSITORY_PERMISSION_DESCRIPTOR = "META-INF/scm/repository-permissions.xml"; private final Collection availableVerbs; private final Collection availableRoles; @Inject public RepositoryPermissionProvider(PluginLoader pluginLoader) { AvailableRepositoryPermissions availablePermissions = readAvailablePermissions(pluginLoader); this.availableVerbs = unmodifiableCollection(new LinkedHashSet<>(availablePermissions.availableVerbs)); this.availableRoles = unmodifiableCollection(new LinkedHashSet<>(availablePermissions.availableRoles.stream().map(r -> new RepositoryRole(r.name, r.verbs.verbs)).collect(Collectors.toList()))); } public Collection availableVerbs() { return availableVerbs; } public Collection availableRoles() { return availableRoles; } private static AvailableRepositoryPermissions readAvailablePermissions(PluginLoader pluginLoader) { Collection availableVerbs = new ArrayList<>(); Collection availableRoles = new ArrayList<>(); try { JAXBContext context = JAXBContext.newInstance(RepositoryPermissionsRoot.class); // Querying permissions from uberClassLoader returns also the permissions from plugin Enumeration descriptorEnum = pluginLoader.getUberClassLoader().getResources(REPOSITORY_PERMISSION_DESCRIPTOR); while (descriptorEnum.hasMoreElements()) { URL descriptorUrl = descriptorEnum.nextElement(); logger.debug("read repository permission descriptor from {}", descriptorUrl); RepositoryPermissionsRoot repositoryPermissionsRoot = parsePermissionDescriptor(context, descriptorUrl); availableVerbs.addAll(repositoryPermissionsRoot.verbs.verbs); mergeRolesInto(availableRoles, repositoryPermissionsRoot.roles.roles); } } 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 new AvailableRepositoryPermissions(availableVerbs, availableRoles); } private static void mergeRolesInto(Collection targetRoles, List additionalRoles) { additionalRoles.forEach(r -> addOrMergeInto(targetRoles, r)); } private static void addOrMergeInto(Collection targetRoles, RoleDescriptor additionalRole) { Optional existingRole = targetRoles .stream() .filter(r -> r.name.equals(additionalRole.name)) .findFirst(); if (existingRole.isPresent()) { existingRole.get().verbs.verbs.addAll(additionalRole.verbs.verbs); } else { targetRoles.add(additionalRole); } } private static RepositoryPermissionsRoot parsePermissionDescriptor(JAXBContext context, URL descriptorUrl) { try { RepositoryPermissionsRoot descriptorWrapper = (RepositoryPermissionsRoot) context.createUnmarshaller().unmarshal( descriptorUrl); logger.trace("repository permissions from {}: {}", descriptorUrl, descriptorWrapper.verbs.verbs); logger.trace("repository roles from {}: {}", descriptorUrl, descriptorWrapper.roles.roles); return descriptorWrapper; } catch (JAXBException ex) { logger.error("could not parse permission descriptor", ex); return new RepositoryPermissionsRoot(); } } private static class AvailableRepositoryPermissions { private final Collection availableVerbs; private final Collection availableRoles; private AvailableRepositoryPermissions(Collection availableVerbs, Collection availableRoles) { this.availableVerbs = unmodifiableCollection(availableVerbs); this.availableRoles = unmodifiableCollection(availableRoles); } } @XmlRootElement(name = "repository-permissions") @XmlAccessorType(XmlAccessType.FIELD) private static class RepositoryPermissionsRoot { private VerbListDescriptor verbs = new VerbListDescriptor(); private RoleListDescriptor roles = new RoleListDescriptor(); } @XmlRootElement(name = "verbs") private static class VerbListDescriptor { @XmlElement(name = "verb") private Set verbs = new LinkedHashSet<>(); } @XmlRootElement(name = "roles") private static class RoleListDescriptor { @XmlElement(name = "role") private List roles = new ArrayList<>(); } @XmlRootElement(name = "role") @XmlAccessorType(XmlAccessType.FIELD) public static class RoleDescriptor { @XmlElement(name = "name") private String name; @XmlElement(name = "verbs") private VerbListDescriptor verbs = new VerbListDescriptor(); } }