mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 00:45:44 +01:00
merge 2.0.0-m3
This commit is contained in:
@@ -33,6 +33,11 @@ class EdisonHalAppender implements HalAppender {
|
||||
embeddedBuilder.with(rel, embedded);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendEmbedded(String rel, List<HalRepresentation> embedded) {
|
||||
embeddedBuilder.with(rel, embedded);
|
||||
}
|
||||
|
||||
private static class EdisonLinkArrayBuilder implements LinkArrayBuilder {
|
||||
|
||||
private final Links.Builder builder;
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import sonia.scm.security.PermissionAssigner;
|
||||
import sonia.scm.security.PermissionDescriptor;
|
||||
import sonia.scm.security.PermissionPermissions;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -47,6 +48,7 @@ public class GroupPermissionResource {
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
public Response getPermissions(@PathParam("id") String id) {
|
||||
PermissionPermissions.read().check();
|
||||
Collection<PermissionDescriptor> permissions = permissionAssigner.readPermissionsForGroup(id);
|
||||
return Response.ok(permissionCollectionToDtoMapper.mapForGroup(permissions, id)).build();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -17,7 +17,7 @@ public class MeDto extends HalRepresentation {
|
||||
private String name;
|
||||
private String displayName;
|
||||
private String mail;
|
||||
private List<String> groups;
|
||||
private Set<String> groups;
|
||||
|
||||
MeDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import sonia.scm.group.GroupNames;
|
||||
import sonia.scm.group.GroupCollector;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserManager;
|
||||
import sonia.scm.user.UserPermissions;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Collections;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
@@ -22,11 +20,13 @@ public class MeDtoFactory extends HalAppenderMapper {
|
||||
|
||||
private final ResourceLinks resourceLinks;
|
||||
private final UserManager userManager;
|
||||
private final GroupCollector groupCollector;
|
||||
|
||||
@Inject
|
||||
public MeDtoFactory(ResourceLinks resourceLinks, UserManager userManager) {
|
||||
public MeDtoFactory(ResourceLinks resourceLinks, UserManager userManager, GroupCollector groupCollector) {
|
||||
this.resourceLinks = resourceLinks;
|
||||
this.userManager = userManager;
|
||||
this.groupCollector = groupCollector;
|
||||
}
|
||||
|
||||
public MeDto create() {
|
||||
@@ -35,16 +35,12 @@ public class MeDtoFactory extends HalAppenderMapper {
|
||||
|
||||
MeDto dto = createDto(user);
|
||||
mapUserProperties(user, dto);
|
||||
mapGroups(principals, dto);
|
||||
mapGroups(user, dto);
|
||||
return dto;
|
||||
}
|
||||
|
||||
private void mapGroups(PrincipalCollection principals, MeDto dto) {
|
||||
Iterable<String> groups = principals.oneByType(GroupNames.class);
|
||||
if (groups == null) {
|
||||
groups = Collections.emptySet();
|
||||
}
|
||||
dto.setGroups(ImmutableList.copyOf(groups));
|
||||
private void mapGroups(User user, MeDto dto) {
|
||||
dto.setGroups(groupCollector.collect(user.getName()));
|
||||
}
|
||||
|
||||
private void mapUserProperties(User user, MeDto dto) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpStatus;
|
||||
import sonia.scm.ConcurrentModificationException;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.RepositoryPermissions;
|
||||
import sonia.scm.repository.api.MergeCommandBuilder;
|
||||
import sonia.scm.repository.api.MergeCommandResult;
|
||||
import sonia.scm.repository.api.MergeDryRunCommandResult;
|
||||
@@ -49,6 +50,7 @@ public class MergeResource {
|
||||
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
|
||||
log.info("Merge in Repository {}/{} from {} to {}", namespace, name, mergeCommand.getSourceRevision(), mergeCommand.getTargetRevision());
|
||||
try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) {
|
||||
RepositoryPermissions.push(repositoryService.getRepository()).check();
|
||||
MergeCommandResult mergeCommandResult = createMergeCommand(mergeCommand, repositoryService).executeMerge();
|
||||
if (mergeCommandResult.isSuccess()) {
|
||||
return Response.noContent().build();
|
||||
@@ -67,14 +69,19 @@ public class MergeResource {
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
public Response dryRun(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid MergeCommandDto mergeCommand) {
|
||||
|
||||
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
|
||||
log.info("Merge in Repository {}/{} from {} to {}", namespace, name, mergeCommand.getSourceRevision(), mergeCommand.getTargetRevision());
|
||||
try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) {
|
||||
MergeDryRunCommandResult mergeCommandResult = createMergeCommand(mergeCommand, repositoryService).dryRun();
|
||||
if (mergeCommandResult.isMergeable()) {
|
||||
return Response.noContent().build();
|
||||
if (RepositoryPermissions.push(repositoryService.getRepository()).isPermitted()) {
|
||||
MergeDryRunCommandResult mergeCommandResult = createMergeCommand(mergeCommand, repositoryService).dryRun();
|
||||
if (mergeCommandResult.isMergeable()) {
|
||||
return Response.noContent().build();
|
||||
} else {
|
||||
throw new ConcurrentModificationException("revision", mergeCommand.getTargetRevision());
|
||||
}
|
||||
} else {
|
||||
throw new ConcurrentModificationException("revision", mergeCommand.getTargetRevision());
|
||||
return Response.noContent().build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import sonia.scm.security.PermissionAssigner;
|
||||
import sonia.scm.security.PermissionDescriptor;
|
||||
import sonia.scm.security.PermissionPermissions;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -48,6 +49,7 @@ public class UserPermissionResource {
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
public Response getPermissions(@PathParam("id") String id) {
|
||||
PermissionPermissions.read().check();
|
||||
Collection<PermissionDescriptor> permissions = permissionAssigner.readPermissionsForUser(id);
|
||||
return Response.ok(permissionCollectionToDtoMapper.mapForUser(permissions, id)).build();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package sonia.scm.group;
|
||||
|
||||
import com.cronutils.utils.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.cache.Cache;
|
||||
import sonia.scm.cache.CacheManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Collect groups for a certain principal.
|
||||
* <strong>Warning</strong>: The class is only for internal use and should never used directly.
|
||||
*/
|
||||
@Singleton
|
||||
public class DefaultGroupCollector implements GroupCollector {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultGroupCollector.class);
|
||||
|
||||
@VisibleForTesting
|
||||
static final String CACHE_NAME = "sonia.cache.externalGroups";
|
||||
|
||||
private final GroupDAO groupDAO;
|
||||
private final Cache<String, Set<String>> cache;
|
||||
private final Set<GroupResolver> groupResolvers;
|
||||
|
||||
@Inject
|
||||
public DefaultGroupCollector(GroupDAO groupDAO, CacheManager cacheManager, Set<GroupResolver> groupResolvers) {
|
||||
this.groupDAO = groupDAO;
|
||||
this.cache = cacheManager.getCache(CACHE_NAME);
|
||||
this.groupResolvers = groupResolvers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> collect(String principal) {
|
||||
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
|
||||
|
||||
builder.add(AUTHENTICATED);
|
||||
builder.addAll(resolveExternalGroups(principal));
|
||||
appendInternalGroups(principal, builder);
|
||||
|
||||
Set<String> groups = builder.build();
|
||||
LOG.debug("collected following groups for principal {}: {}", principal, groups);
|
||||
return groups;
|
||||
}
|
||||
|
||||
private void appendInternalGroups(String principal, ImmutableSet.Builder<String> builder) {
|
||||
for (Group group : groupDAO.getAll()) {
|
||||
if (group.isMember(principal)) {
|
||||
builder.add(group.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> resolveExternalGroups(String principal) {
|
||||
Set<String> externalGroups = cache.get(principal);
|
||||
|
||||
if (externalGroups == null) {
|
||||
ImmutableSet.Builder<String> newExternalGroups = ImmutableSet.builder();
|
||||
|
||||
for (GroupResolver groupResolver : groupResolvers) {
|
||||
newExternalGroups.addAll(groupResolver.resolve(principal));
|
||||
}
|
||||
externalGroups = newExternalGroups.build();
|
||||
cache.put(principal, externalGroups);
|
||||
}
|
||||
return externalGroups;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package sonia.scm.lifecycle.modules;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.throwingproviders.ThrowingProviderBinder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -10,6 +11,7 @@ import sonia.scm.io.DefaultFileSystem;
|
||||
import sonia.scm.io.FileSystem;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.repository.RepositoryLocationResolver;
|
||||
import sonia.scm.repository.xml.MetadataStore;
|
||||
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
|
||||
import sonia.scm.security.CipherHandler;
|
||||
import sonia.scm.security.CipherUtil;
|
||||
@@ -19,15 +21,20 @@ import sonia.scm.store.BlobStoreFactory;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
import sonia.scm.store.DataStoreFactory;
|
||||
import sonia.scm.store.DefaultBlobDirectoryAccess;
|
||||
import sonia.scm.store.FileBlobStoreFactory;
|
||||
import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.JAXBConfigurationStoreFactory;
|
||||
import sonia.scm.store.JAXBDataStoreFactory;
|
||||
import sonia.scm.store.JAXBPropertyFileAccess;
|
||||
import sonia.scm.update.BlobDirectoryAccess;
|
||||
import sonia.scm.update.PropertyFileAccess;
|
||||
import sonia.scm.update.UpdateStepRepositoryMetadataAccess;
|
||||
import sonia.scm.update.V1PropertyDAO;
|
||||
import sonia.scm.update.xml.XmlV1PropertyDAO;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class BootstrapModule extends AbstractModule {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BootstrapModule.class);
|
||||
@@ -65,6 +72,8 @@ public class BootstrapModule extends AbstractModule {
|
||||
bind(PluginLoader.class).toInstance(pluginLoader);
|
||||
bind(V1PropertyDAO.class, XmlV1PropertyDAO.class);
|
||||
bind(PropertyFileAccess.class, JAXBPropertyFileAccess.class);
|
||||
bind(BlobDirectoryAccess.class, DefaultBlobDirectoryAccess.class);
|
||||
bind(new TypeLiteral<UpdateStepRepositoryMetadataAccess<Path>>() {}).to(new TypeLiteral<MetadataStore>() {});
|
||||
}
|
||||
|
||||
private <T> void bind(Class<T> clazz, Class<? extends T> defaultImplementation) {
|
||||
|
||||
@@ -50,13 +50,16 @@ import sonia.scm.cache.CacheManager;
|
||||
import sonia.scm.cache.GuavaCacheManager;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.group.DefaultGroupCollector;
|
||||
import sonia.scm.group.DefaultGroupDisplayManager;
|
||||
import sonia.scm.group.DefaultGroupManager;
|
||||
import sonia.scm.group.GroupCollector;
|
||||
import sonia.scm.group.GroupDAO;
|
||||
import sonia.scm.group.GroupDisplayManager;
|
||||
import sonia.scm.group.GroupManager;
|
||||
import sonia.scm.group.GroupManagerProvider;
|
||||
import sonia.scm.group.xml.XmlGroupDAO;
|
||||
import sonia.scm.migration.MigrationDAO;
|
||||
import sonia.scm.net.SSLContextProvider;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
import sonia.scm.net.ahc.ContentTransformer;
|
||||
@@ -97,6 +100,7 @@ import sonia.scm.template.MustacheTemplateEngine;
|
||||
import sonia.scm.template.TemplateEngine;
|
||||
import sonia.scm.template.TemplateEngineFactory;
|
||||
import sonia.scm.template.TemplateServlet;
|
||||
import sonia.scm.update.repository.DefaultMigrationStrategyDAO;
|
||||
import sonia.scm.user.DefaultUserDisplayManager;
|
||||
import sonia.scm.user.DefaultUserManager;
|
||||
import sonia.scm.user.UserDAO;
|
||||
@@ -183,6 +187,7 @@ class ScmServletModule extends ServletModule {
|
||||
bind(RepositoryDAO.class, XmlRepositoryDAO.class);
|
||||
bind(RepositoryRoleDAO.class, XmlRepositoryRoleDAO.class);
|
||||
bind(RepositoryRoleManager.class).to(DefaultRepositoryRoleManager.class);
|
||||
bind(MigrationDAO.class).to(DefaultMigrationStrategyDAO.class);
|
||||
|
||||
bindDecorated(RepositoryManager.class, DefaultRepositoryManager.class,
|
||||
RepositoryManagerProvider.class);
|
||||
@@ -192,7 +197,7 @@ class ScmServletModule extends ServletModule {
|
||||
bindDecorated(GroupManager.class, DefaultGroupManager.class,
|
||||
GroupManagerProvider.class);
|
||||
bind(GroupDisplayManager.class, DefaultGroupDisplayManager.class);
|
||||
|
||||
bind(GroupCollector.class, DefaultGroupCollector.class);
|
||||
bind(CGIExecutorFactory.class, DefaultCGIExecutorFactory.class);
|
||||
|
||||
// bind sslcontext provider
|
||||
|
||||
@@ -48,6 +48,8 @@ import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserEvent;
|
||||
import sonia.scm.user.UserModificationEvent;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Receives all kinds of events, which affects authorization relevant data and fires an
|
||||
* {@link AuthorizationChangedEvent} if authorization data has changed.
|
||||
@@ -55,6 +57,7 @@ import sonia.scm.user.UserModificationEvent;
|
||||
* @author Sebastian Sdorra
|
||||
* @since 1.52
|
||||
*/
|
||||
@Singleton
|
||||
@EagerSingleton
|
||||
public class AuthorizationChangedEventProducer {
|
||||
|
||||
|
||||
@@ -104,7 +104,6 @@ public class BearerRealm extends AuthenticatingRealm
|
||||
return helper.authenticationInfoBuilder(accessToken.getSubject())
|
||||
.withCredentials(bt.getCredentials())
|
||||
.withScope(Scopes.fromClaims(accessToken.getClaims()))
|
||||
.withGroups(accessToken.getGroups())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -52,17 +52,18 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.cache.Cache;
|
||||
import sonia.scm.cache.CacheManager;
|
||||
import sonia.scm.group.GroupNames;
|
||||
import sonia.scm.group.GroupCollector;
|
||||
import sonia.scm.group.GroupPermissions;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.RepositoryPermission;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryDAO;
|
||||
import sonia.scm.repository.RepositoryPermission;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserPermissions;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
@@ -88,19 +89,21 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
* @param cacheManager
|
||||
* @param cacheManager
|
||||
* @param repositoryDAO
|
||||
* @param securitySystem
|
||||
* @param repositoryPermissionProvider
|
||||
* @param groupCollector
|
||||
*/
|
||||
@Inject
|
||||
public DefaultAuthorizationCollector(CacheManager cacheManager,
|
||||
RepositoryDAO repositoryDAO, SecuritySystem securitySystem, RepositoryPermissionProvider repositoryPermissionProvider)
|
||||
RepositoryDAO repositoryDAO, SecuritySystem securitySystem, RepositoryPermissionProvider repositoryPermissionProvider, GroupCollector groupCollector)
|
||||
{
|
||||
this.cache = cacheManager.getCache(CACHE_NAME);
|
||||
this.repositoryDAO = repositoryDAO;
|
||||
this.securitySystem = securitySystem;
|
||||
this.repositoryPermissionProvider = repositoryPermissionProvider;
|
||||
this.groupCollector = groupCollector;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
@@ -145,16 +148,16 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
|
||||
Preconditions.checkNotNull(user, "no user found in principal collection");
|
||||
|
||||
GroupNames groupNames = principals.oneByType(GroupNames.class);
|
||||
Set<String> groups = groupCollector.collect(user.getName());
|
||||
|
||||
CacheKey cacheKey = new CacheKey(user.getId(), groupNames);
|
||||
CacheKey cacheKey = new CacheKey(user.getId(), groups);
|
||||
|
||||
AuthorizationInfo info = cache.get(cacheKey);
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
logger.trace("collect AuthorizationInfo for user {}", user.getName());
|
||||
info = createAuthorizationInfo(user, groupNames);
|
||||
info = createAuthorizationInfo(user, groups);
|
||||
cache.put(cacheKey, info);
|
||||
}
|
||||
else if (logger.isTraceEnabled())
|
||||
@@ -166,7 +169,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
}
|
||||
|
||||
private void collectGlobalPermissions(Builder<String> builder,
|
||||
final User user, final GroupNames groups)
|
||||
final User user, final Set<String> groups)
|
||||
{
|
||||
Collection<AssignedPermission> globalPermissions =
|
||||
securitySystem.getPermissions((AssignedPermission input) -> isUserPermitted(user, groups, input));
|
||||
@@ -181,7 +184,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
}
|
||||
|
||||
private void collectRepositoryPermissions(Builder<String> builder, User user,
|
||||
GroupNames groups)
|
||||
Set<String> groups)
|
||||
{
|
||||
for (Repository repository : repositoryDAO.getAll())
|
||||
{
|
||||
@@ -190,7 +193,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
}
|
||||
|
||||
private void collectRepositoryPermissions(Builder<String> builder,
|
||||
Repository repository, User user, GroupNames groups)
|
||||
Repository repository, User user, Set<String> groups)
|
||||
{
|
||||
Collection<RepositoryPermission> repositoryPermissions = repository.getPermissions();
|
||||
|
||||
@@ -245,7 +248,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
.getVerbs();
|
||||
}
|
||||
|
||||
private AuthorizationInfo createAuthorizationInfo(User user, GroupNames groups) {
|
||||
private AuthorizationInfo createAuthorizationInfo(User user, Set<String> groups) {
|
||||
Builder<String> builder = ImmutableSet.builder();
|
||||
|
||||
collectGlobalPermissions(builder, user, groups);
|
||||
@@ -279,7 +282,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
private boolean isUserPermitted(User user, GroupNames groups,
|
||||
private boolean isUserPermitted(User user, Set<String> groups,
|
||||
PermissionObject perm)
|
||||
{
|
||||
//J-
|
||||
@@ -314,7 +317,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
*/
|
||||
private static class CacheKey
|
||||
{
|
||||
private CacheKey(String username, GroupNames groupnames)
|
||||
private CacheKey(String username, Set<String> groupnames)
|
||||
{
|
||||
this.username = username;
|
||||
this.groupnames = groupnames;
|
||||
@@ -356,7 +359,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
//~--- fields -------------------------------------------------------------
|
||||
|
||||
/** group names */
|
||||
private final GroupNames groupnames;
|
||||
private final Set<String> groupnames;
|
||||
|
||||
/** username */
|
||||
private final String username;
|
||||
@@ -374,4 +377,5 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
private final SecuritySystem securitySystem;
|
||||
|
||||
private final RepositoryPermissionProvider repositoryPermissionProvider;
|
||||
private final GroupCollector groupCollector;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ package sonia.scm.security;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import org.apache.shiro.authc.AuthenticationException;
|
||||
import org.apache.shiro.authc.AuthenticationInfo;
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
@@ -45,21 +44,16 @@ import org.apache.shiro.authz.AuthorizationInfo;
|
||||
import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
||||
import org.apache.shiro.realm.AuthorizingRealm;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
|
||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||
import sonia.scm.group.GroupNames;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.plugin.Extension;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Default authorizing realm.
|
||||
*
|
||||
@@ -103,6 +97,9 @@ public class DefaultRealm extends AuthorizingRealm
|
||||
matcher.setPasswordService(service);
|
||||
setCredentialsMatcher(helper.wrapCredentialsMatcher(matcher));
|
||||
setAuthenticationTokenClass(UsernamePasswordToken.class);
|
||||
|
||||
// we cache in the AuthorizationCollector
|
||||
setCachingEnabled(false);
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
@@ -149,7 +146,7 @@ public class DefaultRealm extends AuthorizingRealm
|
||||
LOG.trace("principal does not contain scope information, returning all permissions");
|
||||
log(principals, info, null);
|
||||
}
|
||||
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -180,8 +177,6 @@ public class DefaultRealm extends AuthorizingRealm
|
||||
StringBuilder buffer = new StringBuilder("authorization summary: ");
|
||||
|
||||
buffer.append(SEPARATOR).append("username : ").append(collection.getPrimaryPrincipal());
|
||||
buffer.append(SEPARATOR).append("groups : ");
|
||||
append(buffer, collection.oneByType(GroupNames.class));
|
||||
buffer.append(SEPARATOR).append("roles : ");
|
||||
append(buffer, original.getRoles());
|
||||
buffer.append(SEPARATOR).append("scope : ");
|
||||
|
||||
@@ -40,11 +40,9 @@ import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.group.ExternalGroupNames;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -139,12 +137,6 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtAccessTokenBuilder groups(String... groups) {
|
||||
Collections.addAll(this.groups, groups);
|
||||
return this;
|
||||
}
|
||||
|
||||
JwtAccessTokenBuilder refreshExpiration(Instant refreshExpiration) {
|
||||
this.refreshExpiration = refreshExpiration;
|
||||
this.refreshableFor = 0;
|
||||
@@ -206,16 +198,6 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
|
||||
claims.setIssuer(issuer);
|
||||
}
|
||||
|
||||
if (!groups.isEmpty()) {
|
||||
claims.put(JwtAccessToken.GROUPS_CLAIM_KEY, groups);
|
||||
} else {
|
||||
Subject currentSubject = SecurityUtils.getSubject();
|
||||
ExternalGroupNames externalGroupNames = currentSubject.getPrincipals().oneByType(ExternalGroupNames.class);
|
||||
if (externalGroupNames != null) {
|
||||
claims.put(JwtAccessToken.GROUPS_CLAIM_KEY, externalGroupNames.getCollection().toArray(new String[]{}));
|
||||
}
|
||||
}
|
||||
|
||||
// sign token and create compact version
|
||||
String compact = Jwts.builder()
|
||||
.setClaims(claims)
|
||||
|
||||
@@ -7,10 +7,10 @@ import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Strings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.lifecycle.RestartEvent;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.lifecycle.RestartEvent;
|
||||
import sonia.scm.update.repository.DefaultMigrationStrategyDAO;
|
||||
import sonia.scm.update.repository.MigrationStrategy;
|
||||
import sonia.scm.update.repository.MigrationStrategyDao;
|
||||
import sonia.scm.update.repository.V1Repository;
|
||||
import sonia.scm.update.repository.XmlRepositoryV1UpdateStep;
|
||||
import sonia.scm.util.ValidationUtil;
|
||||
@@ -37,10 +37,10 @@ class MigrationWizardServlet extends HttpServlet {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MigrationWizardServlet.class);
|
||||
|
||||
private final XmlRepositoryV1UpdateStep repositoryV1UpdateStep;
|
||||
private final MigrationStrategyDao migrationStrategyDao;
|
||||
private final DefaultMigrationStrategyDAO migrationStrategyDao;
|
||||
|
||||
@Inject
|
||||
MigrationWizardServlet(XmlRepositoryV1UpdateStep repositoryV1UpdateStep, MigrationStrategyDao migrationStrategyDao) {
|
||||
MigrationWizardServlet(XmlRepositoryV1UpdateStep repositoryV1UpdateStep, DefaultMigrationStrategyDAO migrationStrategyDao) {
|
||||
this.repositoryV1UpdateStep = repositoryV1UpdateStep;
|
||||
this.migrationStrategyDao = migrationStrategyDao;
|
||||
}
|
||||
@@ -103,11 +103,12 @@ class MigrationWizardServlet extends HttpServlet {
|
||||
.forEach(
|
||||
entry-> {
|
||||
String id = entry.getId();
|
||||
String protocol = entry.getType();
|
||||
String originalName = entry.getOriginalName();
|
||||
String strategy = req.getParameter("strategy-" + id);
|
||||
String namespace = req.getParameter("namespace-" + id);
|
||||
String name = req.getParameter("name-" + id);
|
||||
migrationStrategyDao.set(id, originalName, MigrationStrategy.valueOf(strategy), namespace, name);
|
||||
migrationStrategyDao.set(id, protocol, originalName, MigrationStrategy.valueOf(strategy), namespace, name);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -55,9 +55,10 @@ public class UpdateEngine {
|
||||
|
||||
private void execute(UpdateStep updateStep) {
|
||||
try {
|
||||
LOG.info("running update step for type {} and version {}",
|
||||
LOG.info("running update step for type {} and version {} (class {})",
|
||||
updateStep.getAffectedDataType(),
|
||||
updateStep.getTargetVersion()
|
||||
updateStep.getTargetVersion(),
|
||||
updateStep.getClass().getName()
|
||||
);
|
||||
updateStep.doUpdate();
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import sonia.scm.migration.MigrationDAO;
|
||||
import sonia.scm.migration.MigrationInfo;
|
||||
import sonia.scm.store.ConfigurationStore;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
@Singleton
|
||||
public class DefaultMigrationStrategyDAO implements MigrationDAO {
|
||||
|
||||
private final RepositoryMigrationPlan plan;
|
||||
private final ConfigurationStore<RepositoryMigrationPlan> store;
|
||||
|
||||
@Inject
|
||||
public DefaultMigrationStrategyDAO(ConfigurationStoreFactory storeFactory) {
|
||||
store = storeFactory.withType(RepositoryMigrationPlan.class).withName("migration-plan").build();
|
||||
this.plan = store.getOptional().orElse(new RepositoryMigrationPlan());
|
||||
}
|
||||
|
||||
public Optional<RepositoryMigrationPlan.RepositoryMigrationEntry> get(String id) {
|
||||
return plan.get(id);
|
||||
}
|
||||
|
||||
public void set(String repositoryId, String protocol, String originalName, MigrationStrategy strategy, String newNamespace, String newName) {
|
||||
plan.set(repositoryId, protocol, originalName, strategy, newNamespace, newName);
|
||||
store.set(plan);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MigrationInfo> getAll() {
|
||||
return plan
|
||||
.getEntries()
|
||||
.stream()
|
||||
.map(e -> new MigrationInfo(e.getRepositoryId(), e.getProtocol(), e.getOriginalName(), e.getNewNamespace(), e.getNewName()))
|
||||
.collect(toList());
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import sonia.scm.store.ConfigurationStore;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
public class MigrationStrategyDao {
|
||||
|
||||
private final RepositoryMigrationPlan plan;
|
||||
private final ConfigurationStore<RepositoryMigrationPlan> store;
|
||||
|
||||
@Inject
|
||||
public MigrationStrategyDao(ConfigurationStoreFactory storeFactory) {
|
||||
store = storeFactory.withType(RepositoryMigrationPlan.class).withName("migration-plan").build();
|
||||
this.plan = store.getOptional().orElse(new RepositoryMigrationPlan());
|
||||
}
|
||||
|
||||
public Optional<RepositoryMigrationPlan.RepositoryMigrationEntry> get(String id) {
|
||||
return plan.get(id);
|
||||
}
|
||||
|
||||
public void set(String repositoryId, String originalName, MigrationStrategy strategy, String newNamespace, String newName) {
|
||||
plan.set(repositoryId, originalName, strategy, newNamespace, newName);
|
||||
store.set(plan);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -29,14 +31,18 @@ class RepositoryMigrationPlan {
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public void set(String repositoryId, String originalName, MigrationStrategy strategy, String newNamespace, String newName) {
|
||||
public Collection<RepositoryMigrationEntry> getEntries() {
|
||||
return Collections.unmodifiableList(entries);
|
||||
}
|
||||
|
||||
public void set(String repositoryId, String protocol, String originalName, MigrationStrategy strategy, String newNamespace, String newName) {
|
||||
Optional<RepositoryMigrationEntry> entry = get(repositoryId);
|
||||
if (entry.isPresent()) {
|
||||
entry.get().setStrategy(strategy);
|
||||
entry.get().setNewNamespace(newNamespace);
|
||||
entry.get().setNewName(newName);
|
||||
} else {
|
||||
entries.add(new RepositoryMigrationEntry(repositoryId, originalName, strategy, newNamespace, newName));
|
||||
entries.add(new RepositoryMigrationEntry(repositoryId, protocol, originalName, strategy, newNamespace, newName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +51,7 @@ class RepositoryMigrationPlan {
|
||||
static class RepositoryMigrationEntry {
|
||||
|
||||
private String repositoryId;
|
||||
private String protocol;
|
||||
private String originalName;
|
||||
private MigrationStrategy dataMigrationStrategy;
|
||||
private String newNamespace;
|
||||
@@ -53,14 +60,23 @@ class RepositoryMigrationPlan {
|
||||
RepositoryMigrationEntry() {
|
||||
}
|
||||
|
||||
RepositoryMigrationEntry(String repositoryId, String originalName, MigrationStrategy dataMigrationStrategy, String newNamespace, String newName) {
|
||||
RepositoryMigrationEntry(String repositoryId, String protocol, String originalName, MigrationStrategy dataMigrationStrategy, String newNamespace, String newName) {
|
||||
this.repositoryId = repositoryId;
|
||||
this.protocol = protocol;
|
||||
this.originalName = originalName;
|
||||
this.dataMigrationStrategy = dataMigrationStrategy;
|
||||
this.newNamespace = newNamespace;
|
||||
this.newName = newName;
|
||||
}
|
||||
|
||||
public String getRepositoryId() {
|
||||
return repositoryId;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public String getOriginalName() {
|
||||
return originalName;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ import static sonia.scm.version.Version.parse;
|
||||
* <li>a new entry in the new <code>repository-paths.xml</code> database is written,</li>
|
||||
* <li>the data directory is moved or copied to a SCM v2 consistent directory. How this is done
|
||||
* can be specified by a strategy (@see {@link MigrationStrategy}), that has to be set in
|
||||
* a database file named <code>migration-plan.xml</code></li> (to create this file, use {@link MigrationStrategyDao}),
|
||||
* a database file named <code>migration-plan.xml</code></li> (to create this file, use {@link DefaultMigrationStrategyDAO}),
|
||||
* and
|
||||
* <li>the new <code>metadata.xml</code> file is created.</li>
|
||||
* </ul>
|
||||
@@ -63,7 +63,7 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
private final XmlRepositoryDAO repositoryDao;
|
||||
private final MigrationStrategyDao migrationStrategyDao;
|
||||
private final DefaultMigrationStrategyDAO migrationStrategyDao;
|
||||
private final Injector injector;
|
||||
private final ConfigurationEntryStore<V1Properties> propertyStore;
|
||||
|
||||
@@ -71,7 +71,7 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
||||
public XmlRepositoryV1UpdateStep(
|
||||
SCMContextProvider contextProvider,
|
||||
XmlRepositoryDAO repositoryDao,
|
||||
MigrationStrategyDao migrationStrategyDao,
|
||||
DefaultMigrationStrategyDAO migrationStrategyDao,
|
||||
Injector injector,
|
||||
ConfigurationEntryStoreFactory configurationEntryStoreFactory
|
||||
) {
|
||||
|
||||
@@ -21,7 +21,10 @@ import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static sonia.scm.version.Version.parse;
|
||||
@@ -29,6 +32,8 @@ import static sonia.scm.version.Version.parse;
|
||||
@Extension
|
||||
public class XmlSecurityV1UpdateStep implements UpdateStep {
|
||||
|
||||
private static final Pattern v1PermissionPattern = Pattern.compile("^repository:\\*:(READ|WRITE|OWNER)$");
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XmlSecurityV1UpdateStep.class);
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
@@ -46,6 +51,50 @@ public class XmlSecurityV1UpdateStep implements UpdateStep {
|
||||
|
||||
forAllAdmins(user -> createSecurityEntry(user, false, securityStore),
|
||||
group -> createSecurityEntry(group, true, securityStore));
|
||||
|
||||
mapV1Permissions(securityStore);
|
||||
}
|
||||
|
||||
private void mapV1Permissions(ConfigurationEntryStore<AssignedPermission> securityStore) throws JAXBException {
|
||||
Path v1SecurityFile = determineConfigDirectory().resolve("securityV1" + StoreConstants.FILE_EXTENSION);
|
||||
|
||||
if (!v1SecurityFile.toFile().exists()) {
|
||||
LOG.info("no v1 file for security found");
|
||||
return;
|
||||
}
|
||||
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(XmlSecurityV1UpdateStep.V1Security.class);
|
||||
V1Security v1Security = (V1Security) jaxbContext.createUnmarshaller().unmarshal(v1SecurityFile.toFile());
|
||||
|
||||
v1Security.entries.forEach(assignedPermission -> {
|
||||
Matcher matcher = v1PermissionPattern.matcher(assignedPermission.value.permission);
|
||||
if (matcher.matches()) {
|
||||
String newPermission = convertRole(matcher.group(1));
|
||||
securityStore.put(new AssignedPermission(
|
||||
assignedPermission.value.name,
|
||||
Boolean.parseBoolean(assignedPermission.value.groupPermission),
|
||||
newPermission
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String convertRole(String role) {
|
||||
String newPermission;
|
||||
switch (role) {
|
||||
case "OWNER":
|
||||
newPermission = "repository:*";
|
||||
break;
|
||||
case "WRITE":
|
||||
newPermission = "repository:read,pull,push:*";
|
||||
break;
|
||||
case "READ":
|
||||
newPermission = "repository:read,pull:*";
|
||||
break;
|
||||
default:
|
||||
newPermission = "";
|
||||
}
|
||||
return newPermission;
|
||||
}
|
||||
|
||||
private void forAllAdmins(Consumer<String> userConsumer, Consumer<String> groupConsumer) throws JAXBException {
|
||||
@@ -70,10 +119,9 @@ public class XmlSecurityV1UpdateStep implements UpdateStep {
|
||||
Arrays.stream(entries.split(",")).forEach(consumer);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Version getTargetVersion() {
|
||||
return parse("2.0.0");
|
||||
return parse("2.0.1");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,4 +150,29 @@ public class XmlSecurityV1UpdateStep implements UpdateStep {
|
||||
@XmlElement(name = "admin-groups")
|
||||
private String adminGroups;
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "configuration")
|
||||
private static class V1Security {
|
||||
@XmlElement(name = "entry")
|
||||
private List<Entry> entries;
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
private static class Entry {
|
||||
@XmlElement(name = "key")
|
||||
private String key;
|
||||
@XmlElement(name = "value")
|
||||
private Value value;
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
private static class Value {
|
||||
@XmlElement(name = "permission")
|
||||
String permission;
|
||||
@XmlElement(name = "name")
|
||||
String name;
|
||||
@XmlElement(name = "group-permission")
|
||||
String groupPermission;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,8 @@ public class XmlUserV1UpdateStep implements UpdateStep {
|
||||
|
||||
@Override
|
||||
public void doUpdate() throws JAXBException {
|
||||
Optional<Path> v1UsersFile = determineV1File();
|
||||
Optional<Path> v1UsersFile = determineV1File("users");
|
||||
determineV1File("security");
|
||||
if (!v1UsersFile.isPresent()) {
|
||||
LOG.info("no v1 file for users found");
|
||||
return;
|
||||
@@ -107,17 +108,17 @@ public class XmlUserV1UpdateStep implements UpdateStep {
|
||||
return configurationEntryStoreFactory.withType(AssignedPermission.class).withName("security").build();
|
||||
}
|
||||
|
||||
private Optional<Path> determineV1File() {
|
||||
Path existingUsersFile = resolveConfigFile("users");
|
||||
Path usersV1File = resolveConfigFile("usersV1");
|
||||
if (existingUsersFile.toFile().exists()) {
|
||||
private Optional<Path> determineV1File(String filename) {
|
||||
Path existingFile = resolveConfigFile(filename);
|
||||
Path v1File = resolveConfigFile(filename + "V1");
|
||||
if (existingFile.toFile().exists()) {
|
||||
try {
|
||||
Files.move(existingUsersFile, usersV1File);
|
||||
Files.move(existingFile, v1File);
|
||||
} catch (IOException e) {
|
||||
throw new UpdateException("could not move old users file to " + usersV1File.toAbsolutePath());
|
||||
throw new UpdateException("could not move old " + filename + " file to " + v1File.toAbsolutePath());
|
||||
}
|
||||
LOG.info("moved old users file to {}", usersV1File.toAbsolutePath());
|
||||
return of(usersV1File);
|
||||
LOG.info("moved old " + filename + " file to {}", v1File.toAbsolutePath());
|
||||
return of(v1File);
|
||||
}
|
||||
return empty();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package sonia.scm.web.filter;
|
||||
|
||||
import sonia.scm.Priority;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.filter.Filters;
|
||||
import sonia.scm.filter.WebElement;
|
||||
import sonia.scm.web.UserAgentParser;
|
||||
import sonia.scm.web.WebTokenGenerator;
|
||||
import sonia.scm.web.protocol.HttpProtocolServlet;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Set;
|
||||
|
||||
@Priority(Filters.PRIORITY_AUTHENTICATION)
|
||||
@WebElement(value = HttpProtocolServlet.PATTERN)
|
||||
public class DefaultHttpProtocolServletAuthenticationFilter extends HttpProtocolServletAuthenticationFilterBase {
|
||||
|
||||
@Inject
|
||||
public DefaultHttpProtocolServletAuthenticationFilter(ScmConfiguration configuration, Set<WebTokenGenerator> tokenGenerators, UserAgentParser userAgentParser) {
|
||||
super(configuration, tokenGenerators, userAgentParser);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package sonia.scm.web.filter;
|
||||
|
||||
import sonia.scm.Priority;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.filter.Filters;
|
||||
import sonia.scm.filter.WebElement;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.web.UserAgent;
|
||||
import sonia.scm.web.UserAgentParser;
|
||||
import sonia.scm.web.WebTokenGenerator;
|
||||
import sonia.scm.web.protocol.HttpProtocolServlet;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
@Priority(Filters.PRIORITY_AUTHENTICATION)
|
||||
@WebElement(value = HttpProtocolServlet.PATTERN)
|
||||
public class HttpProtocolServletAuthenticationFilter extends AuthenticationFilter {
|
||||
|
||||
private final UserAgentParser userAgentParser;
|
||||
|
||||
@Inject
|
||||
public HttpProtocolServletAuthenticationFilter(
|
||||
ScmConfiguration configuration,
|
||||
Set<WebTokenGenerator> tokenGenerators,
|
||||
UserAgentParser userAgentParser) {
|
||||
super(configuration, tokenGenerators);
|
||||
this.userAgentParser = userAgentParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleUnauthorized(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
UserAgent userAgent = userAgentParser.parse(request);
|
||||
if (userAgent.isBrowser()) {
|
||||
// we can proceed the filter chain because the HttpProtocolServlet will render the ui if the client is a browser
|
||||
chain.doFilter(request, response);
|
||||
} else {
|
||||
HttpUtil.sendUnauthorized(request, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import sonia.scm.repository.spi.HttpScmProtocol;
|
||||
import sonia.scm.web.UserAgent;
|
||||
import sonia.scm.web.UserAgentParser;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -33,17 +32,15 @@ public class HttpProtocolServlet extends HttpServlet {
|
||||
public static final String PATTERN = PATH + "/*";
|
||||
|
||||
private final RepositoryServiceFactory serviceFactory;
|
||||
|
||||
private final Provider<HttpServletRequest> requestProvider;
|
||||
|
||||
private final NamespaceAndNameFromPathExtractor pathExtractor;
|
||||
private final PushStateDispatcher dispatcher;
|
||||
private final UserAgentParser userAgentParser;
|
||||
|
||||
|
||||
@Inject
|
||||
public HttpProtocolServlet(RepositoryServiceFactory serviceFactory, Provider<HttpServletRequest> requestProvider, PushStateDispatcher dispatcher, UserAgentParser userAgentParser) {
|
||||
public HttpProtocolServlet(RepositoryServiceFactory serviceFactory, NamespaceAndNameFromPathExtractor pathExtractor, PushStateDispatcher dispatcher, UserAgentParser userAgentParser) {
|
||||
this.serviceFactory = serviceFactory;
|
||||
this.requestProvider = requestProvider;
|
||||
this.pathExtractor = pathExtractor;
|
||||
this.dispatcher = dispatcher;
|
||||
this.userAgentParser = userAgentParser;
|
||||
}
|
||||
@@ -55,9 +52,8 @@ public class HttpProtocolServlet extends HttpServlet {
|
||||
log.trace("dispatch browser request for user agent {}", userAgent);
|
||||
dispatcher.dispatch(request, response, request.getRequestURI());
|
||||
} else {
|
||||
|
||||
String pathInfo = request.getPathInfo();
|
||||
Optional<NamespaceAndName> namespaceAndName = NamespaceAndNameFromPathExtractor.fromUri(pathInfo);
|
||||
Optional<NamespaceAndName> namespaceAndName = pathExtractor.fromUri(pathInfo);
|
||||
if (namespaceAndName.isPresent()) {
|
||||
service(request, response, namespaceAndName.get());
|
||||
} else {
|
||||
@@ -69,7 +65,7 @@ public class HttpProtocolServlet extends HttpServlet {
|
||||
|
||||
private void service(HttpServletRequest req, HttpServletResponse resp, NamespaceAndName namespaceAndName) throws IOException, ServletException {
|
||||
try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) {
|
||||
requestProvider.get().setAttribute(DefaultRepositoryProvider.ATTRIBUTE_NAME, repositoryService.getRepository());
|
||||
req.setAttribute(DefaultRepositoryProvider.ATTRIBUTE_NAME, repositoryService.getRepository());
|
||||
HttpScmProtocol protocol = repositoryService.getProtocol(HttpScmProtocol.class);
|
||||
protocol.serve(req, resp, getServletConfig());
|
||||
} catch (NotFoundException e) {
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
package sonia.scm.web.protocol;
|
||||
|
||||
import sonia.scm.Type;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
|
||||
final class NamespaceAndNameFromPathExtractor {
|
||||
|
||||
private NamespaceAndNameFromPathExtractor() {}
|
||||
private final Set<String> types;
|
||||
|
||||
static Optional<NamespaceAndName> fromUri(String uri) {
|
||||
@Inject
|
||||
public NamespaceAndNameFromPathExtractor(RepositoryManager repositoryManager) {
|
||||
this.types = repositoryManager.getConfiguredTypes()
|
||||
.stream()
|
||||
.map(Type::getName)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
Optional<NamespaceAndName> fromUri(String uri) {
|
||||
if (uri.startsWith(HttpUtil.SEPARATOR_PATH)) {
|
||||
uri = uri.substring(1);
|
||||
}
|
||||
@@ -30,12 +43,13 @@ final class NamespaceAndNameFromPathExtractor {
|
||||
}
|
||||
|
||||
String name = uri.substring(endOfNamespace + 1, nameIndex);
|
||||
|
||||
int nameDotIndex = name.indexOf('.');
|
||||
int nameDotIndex = name.lastIndexOf('.');
|
||||
if (nameDotIndex >= 0) {
|
||||
return of(new NamespaceAndName(namespace, name.substring(0, nameDotIndex)));
|
||||
} else {
|
||||
return of(new NamespaceAndName(namespace, name));
|
||||
String suffix = name.substring(nameDotIndex + 1);
|
||||
if (types.contains(suffix)) {
|
||||
name = name.substring(0, nameDotIndex);
|
||||
}
|
||||
}
|
||||
return of(new NamespaceAndName(namespace, name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ package sonia.scm.web.security;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||
@@ -46,21 +45,17 @@ import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.subject.support.SubjectThreadState;
|
||||
import org.apache.shiro.util.ThreadContext;
|
||||
import org.apache.shiro.util.ThreadState;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.group.GroupNames;
|
||||
import sonia.scm.security.Role;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.util.AssertUtil;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import javax.xml.bind.JAXB;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.xml.bind.JAXB;
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -161,7 +156,6 @@ public class DefaultAdministrationContext implements AdministrationContext
|
||||
|
||||
collection.add(adminUser.getId(), REALM);
|
||||
collection.add(adminUser, REALM);
|
||||
collection.add(new GroupNames(), REALM);
|
||||
collection.add(AdministrationContextMarker.MARKER, REALM);
|
||||
|
||||
return collection;
|
||||
|
||||
Reference in New Issue
Block a user