Implement namespace configurations & permissions

Co-authored-by: Eduard Heimbuch <eduard.heimbuch@cloudogu.com>
Co-authored-by: René Pfeuffer <rene.pfeuffer@cloudogu.com>

Reviewed-by: Eduard Heimbuch <eduard.heimbuch@cloudogu.com>
This commit is contained in:
Konstantin Schaper
2023-05-25 18:51:29 +02:00
parent 9f9aebf1d5
commit b812922142
27 changed files with 302 additions and 116 deletions

View File

@@ -27,10 +27,13 @@ package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@SuppressWarnings("java:S2160") // we don't need equals and hashcode
public class NamespaceDto extends HalRepresentation {
private String namespace;

View File

@@ -24,32 +24,48 @@
package sonia.scm.api.v2.resources;
import com.google.common.annotations.VisibleForTesting;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.Link;
import de.otto.edison.hal.Links;
import org.mapstruct.InjectionStrategy;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ObjectFactory;
import sonia.scm.repository.Namespace;
import sonia.scm.repository.NamespaceManager;
import sonia.scm.repository.NamespacePermissions;
import sonia.scm.search.SearchEngine;
import sonia.scm.search.SearchableType;
import sonia.scm.web.EdisonHalAppender;
import javax.inject.Inject;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Link.linkBuilder;
import static de.otto.edison.hal.Links.linkingTo;
class NamespaceToNamespaceDtoMapper {
private final ResourceLinks links;
private final SearchEngine searchEngine;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@Mapper
public abstract class NamespaceToNamespaceDtoMapper extends BaseMapper<Namespace, NamespaceDto> {
@Inject
NamespaceToNamespaceDtoMapper(ResourceLinks links, SearchEngine searchEngine) {
this.links = links;
this.searchEngine = searchEngine;
}
protected ResourceLinks links;
@Inject
protected SearchEngine searchEngine;
@Inject
protected NamespaceManager namespaceManager;
NamespaceDto map(String namespace) {
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
public abstract NamespaceDto map(String namespace);
@ObjectFactory
NamespaceDto createDto(String namespace) {
Links.Builder linkingTo = linkingTo();
linkingTo
.self(links.namespace().self(namespace))
@@ -61,9 +77,30 @@ class NamespaceToNamespaceDtoMapper {
}
linkingTo.array(searchLinks(namespace));
linkingTo.single(link("searchableTypes", links.searchableTypes().searchableTypesForNamespace(namespace)));
Optional<Namespace> optionalNamespace = namespaceManager.get(namespace);
if (optionalNamespace.isPresent()) {
Embedded.Builder embeddedBuilder = embeddedBuilder();
applyEnrichers(new EdisonHalAppender(linkingTo, embeddedBuilder), optionalNamespace.get(), namespace);
}
return new NamespaceDto(namespace, linkingTo.build());
}
@VisibleForTesting
void setLinks(ResourceLinks links) {
this.links = links;
}
@VisibleForTesting
void setSearchEngine(SearchEngine searchEngine) {
this.searchEngine = searchEngine;
}
@VisibleForTesting
void setNamespaceManager(NamespaceManager namespaceManager) {
this.namespaceManager = namespaceManager;
}
private List<Link> searchLinks(String namespace) {
return searchEngine.getSearchableTypes().stream()
.filter(SearchableType::limitableToNamespace)

View File

@@ -28,6 +28,7 @@ import sonia.scm.store.DataStore;
import sonia.scm.store.DataStoreFactory;
import javax.inject.Inject;
import java.util.Collection;
import java.util.Optional;
public class NamespaceDao {
@@ -50,4 +51,8 @@ public class NamespaceDao {
public void delete(String namespace) {
store.remove(namespace);
}
public Collection<Namespace> allWithPermissions() {
return store.getAll().values();
}
}

View File

@@ -58,35 +58,25 @@ import java.util.Set;
import static java.util.Collections.emptySet;
/**
*
* @author Sebastian Sdorra
*/
@Singleton
@Extension
public class DefaultAuthorizationCollector implements AuthorizationCollector
{
public class DefaultAuthorizationCollector implements AuthorizationCollector {
/** Field description */
private static final String CACHE_NAME = "sonia.cache.authorizing";
/**
* the logger for DefaultAuthorizationCollector
*/
private static final Logger logger =
LoggerFactory.getLogger(DefaultAuthorizationCollector.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
* @param cacheManager
* @param repositoryDAO
* @param securitySystem
* @param repositoryPermissionProvider
* @param groupCollector
* @param namespaceDao
*/
/** authorization cache */
private final Cache<CacheKey, AuthorizationInfo> cache;
private final RepositoryDAO repositoryDAO;
private final NamespaceDao namespaceDao;
private final SecuritySystem securitySystem;
private final RepositoryPermissionProvider repositoryPermissionProvider;
private final GroupCollector groupCollector;
@Inject
public DefaultAuthorizationCollector(CacheManager cacheManager,
RepositoryDAO repositoryDAO, SecuritySystem securitySystem, RepositoryPermissionProvider repositoryPermissionProvider, GroupCollector groupCollector, NamespaceDao namespaceDao)
@@ -99,14 +89,6 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
this.namespaceDao = namespaceDao;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@VisibleForTesting
AuthorizationInfo collect()
{
@@ -125,13 +107,6 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
return authorizationInfo;
}
/**
* Method description
*
* @param principals
*
* @return
*/
@Override
public AuthorizationInfo collect(PrincipalCollection principals)
{
@@ -176,6 +151,35 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
}
}
private void collectNamespacePermissions(Builder<String> builder, User user, Set<String> groups) {
for (Namespace namespace : namespaceDao.allWithPermissions()) {
collectNamespacePermissions(builder, namespace, user, groups);
}
}
private void collectNamespacePermissions(Builder<String> builder, Namespace namespace, User user, Set<String> groups) {
for (RepositoryPermission permission : namespace.getPermissions()) {
if (isUserPermitted(user, groups, permission)) {
addNamespacePermission(builder, namespace, user, permission);
}
}
}
private void addNamespacePermission(Builder<String> builder, Namespace namespace, User user, RepositoryPermission permission) {
Collection<String> verbs = getVerbs(permission);
if (!verbs.isEmpty())
{
String perm = "namespace:" + String.join(",", verbs) + ":" + namespace.getId();
if (logger.isTraceEnabled())
{
logger.trace("add namespace permission {} for user {} at namespace {}",
perm, user.getName(), namespace.getNamespace());
}
builder.add(perm);
}
}
private void collectRepositoryPermissions(Builder<String> builder, User user,
Set<String> groups)
{
@@ -245,6 +249,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
collectGlobalPermissions(builder, user, groups);
collectRepositoryPermissions(builder, user, groups);
collectNamespacePermissions(builder, user, groups);
builder.add(canReadOwnUser(user));
if (!Authentications.isSubjectAnonymous(user.getName())) {
builder.add(getUserAutocompletePermission());
@@ -254,7 +259,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
builder.add(getPublicKeyPermission(user));
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(ImmutableSet.of(Role.USER));
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(Set.of(Role.USER));
info.addStringPermissions(builder.build());
return info;
@@ -314,24 +319,17 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
cache.clear();
}
//~--- inner classes --------------------------------------------------------
/**
* Cache key.
*/
private static class CacheKey
{
private final Set<String> groupnames;
private final String username;
private CacheKey(String username, Set<String> groupnames)
{
this.username = username;
this.groupnames = groupnames;
}
//~--- methods ------------------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj)
{
@@ -351,36 +349,10 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
&& Objects.equal(groupnames, other.groupnames);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode()
{
return Objects.hashCode(username, groupnames);
}
//~--- fields -------------------------------------------------------------
/** group names */
private final Set<String> groupnames;
/** username */
private final String username;
}
//~--- fields ---------------------------------------------------------------
/** authorization cache */
private final Cache<CacheKey, AuthorizationInfo> cache;
/** repository dao */
private final RepositoryDAO repositoryDAO;
/** security system */
private final SecuritySystem securitySystem;
private final RepositoryPermissionProvider repositoryPermissionProvider;
private final GroupCollector groupCollector;
private final NamespaceDao namespaceDao;
}