Merged 2.0.0-m3 into feature/global_config_v2_endpoint

This commit is contained in:
Johannes Schnatterer
2018-08-01 10:30:38 +02:00
358 changed files with 24149 additions and 21316 deletions

View File

@@ -38,7 +38,6 @@ package sonia.scm;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Provider;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Names;
import com.google.inject.servlet.RequestScoped;
import com.google.inject.servlet.ServletModule;
import com.google.inject.throwingproviders.ThrowingProviderBinder;
@@ -57,16 +56,8 @@ import sonia.scm.group.xml.XmlGroupDAO;
import sonia.scm.io.DefaultFileSystem;
import sonia.scm.io.FileSystem;
import sonia.scm.net.SSLContextProvider;
import sonia.scm.net.ahc.AdvancedHttpClient;
import sonia.scm.net.ahc.ContentTransformer;
import sonia.scm.net.ahc.DefaultAdvancedHttpClient;
import sonia.scm.net.ahc.JsonContentTransformer;
import sonia.scm.net.ahc.XmlContentTransformer;
import sonia.scm.plugin.DefaultPluginLoader;
import sonia.scm.plugin.DefaultPluginManager;
import sonia.scm.plugin.ExtensionProcessor;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.PluginManager;
import sonia.scm.net.ahc.*;
import sonia.scm.plugin.*;
import sonia.scm.repository.*;
import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.repository.api.RepositoryServiceFactory;
@@ -78,32 +69,12 @@ import sonia.scm.resources.ResourceManager;
import sonia.scm.resources.ScriptResourceServlet;
import sonia.scm.schedule.QuartzScheduler;
import sonia.scm.schedule.Scheduler;
import sonia.scm.security.AuthorizationChangedEventProducer;
import sonia.scm.security.CipherHandler;
import sonia.scm.security.CipherUtil;
import sonia.scm.security.ConfigurableLoginAttemptHandler;
import sonia.scm.security.DefaultKeyGenerator;
import sonia.scm.security.DefaultSecuritySystem;
import sonia.scm.security.KeyGenerator;
import sonia.scm.security.LoginAttemptHandler;
import sonia.scm.security.SecuritySystem;
import sonia.scm.store.BlobStoreFactory;
import sonia.scm.store.ConfigurationEntryStoreFactory;
import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.store.DataStoreFactory;
import sonia.scm.store.FileBlobStoreFactory;
import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
import sonia.scm.store.JAXBConfigurationStoreFactory;
import sonia.scm.store.JAXBDataStoreFactory;
import sonia.scm.security.*;
import sonia.scm.store.*;
import sonia.scm.template.MustacheTemplateEngine;
import sonia.scm.template.TemplateEngine;
import sonia.scm.template.TemplateEngineFactory;
import sonia.scm.template.TemplateServlet;
import sonia.scm.url.RestJsonUrlProvider;
import sonia.scm.url.RestXmlUrlProvider;
import sonia.scm.url.UrlProvider;
import sonia.scm.url.UrlProviderFactory;
import sonia.scm.url.WebUIUrlProvider;
import sonia.scm.user.DefaultUserManager;
import sonia.scm.user.UserDAO;
import sonia.scm.user.UserManager;
@@ -223,7 +194,9 @@ public class ScmServletModule extends ServletModule
ScmConfiguration config = getScmConfiguration();
CipherUtil cu = CipherUtil.getInstance();
bind(NamespaceStrategy.class).toProvider(NamespaceStrategyProvider.class);
// bind repository provider
ThrowingProviderBinder.create(binder()).bind(
RepositoryProvider.class, Repository.class).to(
@@ -303,17 +276,6 @@ public class ScmServletModule extends ServletModule
bind(ResourceManager.class, DefaultResourceManager.class);
}
// bind url provider staff
bind(UrlProvider.class).annotatedWith(
Names.named(UrlProviderFactory.TYPE_RESTAPI_JSON)).toProvider(
RestJsonUrlProvider.class);
bind(UrlProvider.class).annotatedWith(
Names.named(UrlProviderFactory.TYPE_RESTAPI_XML)).toProvider(
RestXmlUrlProvider.class);
bind(UrlProvider.class).annotatedWith(
Names.named(UrlProviderFactory.TYPE_WUI)).toProvider(
WebUIUrlProvider.class);
// bind repository service factory
bind(RepositoryServiceFactory.class);
@@ -351,10 +313,10 @@ public class ScmServletModule extends ServletModule
// bind events
// bind(LastModifiedUpdateListener.class);
Class<? extends NamespaceStrategy> namespaceStrategy = extensionProcessor.byExtensionPoint(NamespaceStrategy.class).iterator().next();
bind(NamespaceStrategy.class, namespaceStrategy);
}
/**
* Method description
*

View File

@@ -48,13 +48,8 @@ import sonia.scm.util.AssertUtil;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.Util;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;

View File

@@ -47,22 +47,8 @@ import sonia.scm.group.GroupException;
import sonia.scm.group.GroupManager;
import sonia.scm.security.Role;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.util.Collection;
//~--- JDK imports ------------------------------------------------------------

View File

@@ -50,15 +50,7 @@ import org.slf4j.LoggerFactory;
import sonia.scm.NotSupportedFeatuerException;
import sonia.scm.Type;
import sonia.scm.api.rest.RestActionUploadResult;
import sonia.scm.repository.AdvancedImportHandler;
import sonia.scm.repository.ImportHandler;
import sonia.scm.repository.ImportResult;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryAlreadyExistsException;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RepositoryHandler;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryType;
import sonia.scm.repository.*;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
@@ -66,21 +58,8 @@ import sonia.scm.repository.api.UnbundleCommandBuilder;
import sonia.scm.security.Role;
import sonia.scm.util.IOUtil;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@@ -543,7 +522,8 @@ public class RepositoryImportResource
try
{
repository = new Repository(null, type, name);
// TODO #8783
// repository = new Repository(null, type, name);
manager.create(repository);
}
catch (RepositoryAlreadyExistsException ex)
@@ -738,7 +718,8 @@ public class RepositoryImportResource
{
for (String repositoryName : repositoryNames)
{
Repository repository = manager.get(type, repositoryName);
// TODO #8783
/*Repository repository = null; //manager.get(type, repositoryName);
if (repository != null)
{
@@ -748,7 +729,7 @@ public class RepositoryImportResource
{
logger.warn("could not find imported repository {}",
repositoryName);
}
}*/
}
}
}

View File

@@ -46,51 +46,16 @@ import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.BlameResult;
import sonia.scm.repository.Branches;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.HealthChecker;
import sonia.scm.repository.Permission;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RepositoryIsNotArchivedException;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.Tags;
import sonia.scm.repository.api.BlameCommandBuilder;
import sonia.scm.repository.api.BrowseCommandBuilder;
import sonia.scm.repository.api.CatCommandBuilder;
import sonia.scm.repository.api.CommandNotSupportedException;
import sonia.scm.repository.api.DiffCommandBuilder;
import sonia.scm.repository.api.DiffFormat;
import sonia.scm.repository.api.LogCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.*;
import sonia.scm.repository.api.*;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -556,42 +521,6 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
return response;
}
/**
* Returns the {@link Repository} with the specified type and name.
*
* @param type the type of the repository
* @param name the name of the repository
*
* @return the {@link Repository} with the specified type and name
*/
@GET
@Path("{type: [a-z]+}/{name: .*}")
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 404, condition = "not found, no repository with the specified type and name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(Repository.class)
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response getByTypeAndName(@PathParam("type") String type,
@PathParam("name") String name)
{
Response response;
Repository repository = repositoryManager.get(type, name);
if (repository != null)
{
prepareForReturn(repository);
response = Response.ok(repository).build();
}
else
{
response = Response.status(Response.Status.NOT_FOUND).build();
}
return response;
}
/**
* Returns the {@link Changeset} from the given repository
* with the specified revision.

View File

@@ -44,8 +44,6 @@ import com.google.inject.Inject;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryTypePredicate;
import sonia.scm.url.UrlProvider;
import sonia.scm.url.UrlProviderFactory;
import sonia.scm.util.HttpUtil;
//~--- JDK imports ------------------------------------------------------------
@@ -106,19 +104,15 @@ public class RepositoryRootResource
*/
@GET
@Produces(MediaType.TEXT_HTML)
public Viewable renderRepositoriesRoot(@Context HttpServletRequest request,
@PathParam("type") final String type)
throws IOException
public Viewable renderRepositoriesRoot(@Context HttpServletRequest request, @PathParam("type") final String type)
{
String baseUrl = HttpUtil.getCompleteUrl(request);
UrlProvider uiUrlProvider = UrlProviderFactory.createUrlProvider(baseUrl,
UrlProviderFactory.TYPE_WUI);
//J-
Collection<RepositoryTemplateElement> unsortedRepositories =
Collections2.transform(
Collections2.filter(
repositoryManager.getAll(), new RepositoryTypePredicate(type))
, new RepositoryTransformFunction(uiUrlProvider, baseUrl)
, new RepositoryTransformFunction(baseUrl)
);
List<RepositoryTemplateElement> repositories = Ordering.from(
@@ -149,43 +143,16 @@ public class RepositoryRootResource
*
*
* @param repository
* @param uiUrlProvider
* @param baseUrl
*/
public RepositoryTemplateElement(Repository repository,
UrlProvider uiUrlProvider, String baseUrl)
public RepositoryTemplateElement(Repository repository, String baseUrl)
{
this.repository = repository;
this.urlProvider = uiUrlProvider;
this.baseUrl = baseUrl;
}
//~--- get methods --------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public String getCommitUrl()
{
return urlProvider.getRepositoryUrlProvider().getChangesetUrl(
repository.getId(), 0, 20);
}
/**
* Method description
*
*
* @return
*/
public String getDetailUrl()
{
return urlProvider.getRepositoryUrlProvider().getDetailUrl(
repository.getId());
}
/**
* Method description
*
@@ -208,18 +175,6 @@ public class RepositoryRootResource
return repository;
}
/**
* Method description
*
*
* @return
*/
public String getSourceUrl()
{
return urlProvider.getRepositoryUrlProvider().getBrowseUrl(
repository.getId(), null, null);
}
/**
* Method description
*
@@ -239,8 +194,6 @@ public class RepositoryRootResource
/** Field description */
private Repository repository;
/** Field description */
private UrlProvider urlProvider;
}
@@ -284,20 +237,8 @@ public class RepositoryRootResource
implements Function<Repository, RepositoryTemplateElement>
{
/**
* Constructs ...
*
*
*
*
* @param request
* @param repositoryManager
* @param urlProvider
* @param baseUrl
*/
public RepositoryTransformFunction(UrlProvider urlProvider, String baseUrl)
public RepositoryTransformFunction(String baseUrl)
{
this.urlProvider = urlProvider;
this.baseUrl = baseUrl;
}
@@ -314,15 +255,12 @@ public class RepositoryRootResource
@Override
public RepositoryTemplateElement apply(Repository repository)
{
return new RepositoryTemplateElement(repository, urlProvider, baseUrl);
return new RepositoryTemplateElement(repository, baseUrl);
}
//~--- fields -------------------------------------------------------------
/** Field description */
private String baseUrl;
/** Field description */
private UrlProvider urlProvider;
}
}

View File

@@ -50,21 +50,8 @@ import sonia.scm.user.UserManager;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.Util;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.util.Collection;
//~--- JDK imports ------------------------------------------------------------

View File

@@ -0,0 +1,51 @@
package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Strings;
import javax.ws.rs.FormParam;
import java.util.List;
public class AuthenticationRequestDto {
@FormParam("grant_type")
@JsonProperty("grant_type")
private String grantType;
@FormParam("username")
private String username;
@FormParam("password")
private String password;
@FormParam("cookie")
private boolean cookie;
@FormParam("scope")
private List<String> scope;
public String getGrantType() {
return grantType;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public boolean isCookie() {
return cookie;
}
public List<String> getScope() {
return scope;
}
public boolean isValid() {
// password is currently the only valid grant_type
return "password".equals(grantType) && !Strings.isNullOrEmpty(username) && !Strings.isNullOrEmpty(password);
}
}

View File

@@ -0,0 +1,140 @@
package sonia.scm.api.v2.resources;
import com.google.inject.Inject;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.security.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Path(AuthenticationResource.PATH)
public class AuthenticationResource {
private static final Logger LOG = LoggerFactory.getLogger(AuthenticationResource.class);
static final String PATH = "v2/auth";
private final AccessTokenBuilderFactory tokenBuilderFactory;
private final AccessTokenCookieIssuer cookieIssuer;
@Inject
public AuthenticationResource(AccessTokenBuilderFactory tokenBuilderFactory, AccessTokenCookieIssuer cookieIssuer)
{
this.tokenBuilderFactory = tokenBuilderFactory;
this.cookieIssuer = cookieIssuer;
}
@POST
@Path("access_token")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 400, condition = "bad request, required parameter is missing"),
@ResponseCode(code = 401, condition = "unauthorized, the specified username or password is wrong"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response authenticateViaForm(
@Context HttpServletRequest request,
@Context HttpServletResponse response,
@BeanParam AuthenticationRequestDto authentication
) {
return authenticate(request, response, authentication);
}
@POST
@Path("access_token")
@Consumes(MediaType.APPLICATION_JSON)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 400, condition = "bad request, required parameter is missing"),
@ResponseCode(code = 401, condition = "unauthorized, the specified username or password is wrong"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response authenticateViaJSONBody(
@Context HttpServletRequest request,
@Context HttpServletResponse response,
AuthenticationRequestDto authentication
) {
return authenticate(request, response, authentication);
}
private Response authenticate(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationRequestDto authentication
) {
if (!authentication.isValid()) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
Response res;
Subject subject = SecurityUtils.getSubject();
try
{
subject.login(Tokens.createAuthenticationToken(request, authentication.getUsername(), authentication.getPassword()));
AccessTokenBuilder tokenBuilder = tokenBuilderFactory.create();
if ( authentication.getScope() != null ) {
tokenBuilder.scope(Scope.valueOf(authentication.getScope()));
}
AccessToken token = tokenBuilder.build();
if (authentication.isCookie()) {
cookieIssuer.authenticate(request, response, token);
res = Response.noContent().build();
} else {
res = Response.ok( token.compact() ).build();
}
}
catch (AuthenticationException ex)
{
if (LOG.isTraceEnabled())
{
LOG.trace("authentication failed for user ".concat(authentication.getUsername()), ex);
}
else
{
LOG.warn("authentication failed for user {}", authentication.getUsername());
}
// TODO DisabledAccountException, ExcessiveAttemptsException for ui?
return Response.status(Response.Status.UNAUTHORIZED).build();
}
return res;
}
@DELETE
@Path("access_token")
@StatusCodes({
@ResponseCode(code = 204, condition = "success"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response logout(@Context HttpServletRequest request, @Context HttpServletResponse response)
{
Subject subject = SecurityUtils.getSubject();
subject.logout();
// remove authentication cookie
cookieIssuer.invalidate(request, response);
// TODO anonymous access ??
return Response.noContent().build();
}
}

View File

@@ -0,0 +1,42 @@
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;
import java.util.Set;
@NoArgsConstructor
@Getter
@Setter
public class ConfigDto extends HalRepresentation {
private String proxyPassword;
private int proxyPort;
private String proxyServer;
private String proxyUser;
private boolean enableProxy;
private String realmDescription;
private boolean enableRepositoryArchive;
private boolean disableGroupingGrid;
private String dateFormat;
private boolean anonymousAccessEnabled;
private Set<String> adminGroups;
private Set<String> adminUsers;
private String baseUrl;
private boolean forceBaseUrl;
private int loginAttemptLimit;
private Set<String> proxyExcludes;
private boolean skipFailedAuthenticators;
private String pluginUrl;
private long loginAttemptLimitTimeout;
private boolean enabledXsrfProtection;
@Override
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
protected HalRepresentation add(Links links) {
return super.add(links);
}
}

View File

@@ -0,0 +1,12 @@
package sonia.scm.api.v2.resources;
import org.mapstruct.Mapper;
import sonia.scm.config.ScmConfiguration;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@Mapper
public abstract class ConfigDtoToScmConfigurationMapper {
public abstract ScmConfiguration map(ConfigDto dto);
}

View File

@@ -0,0 +1,87 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.util.ScmConfigurationUtil;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
@Path(ConfigResource.CONFIG_PATH_V2)
public class ConfigResource {
static final String CONFIG_PATH_V2 = "v2/config";
private final ConfigDtoToScmConfigurationMapper dtoToConfigMapper;
private final ScmConfigurationToConfigDtoMapper configToDtoMapper;
private final ScmConfiguration configuration;
@Inject
public ConfigResource(ConfigDtoToScmConfigurationMapper dtoToConfigMapper, ScmConfigurationToConfigDtoMapper configToDtoMapper, ScmConfiguration configuration) {
this.dtoToConfigMapper = dtoToConfigMapper;
this.configToDtoMapper = configToDtoMapper;
this.configuration = configuration;
}
/**
* Returns the global scm config.
*/
@GET
@Path("")
@Produces(VndMediaType.CONFIG)
@TypeHint(UserDto.class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the global config"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response get() {
// We do this permission check in Resource and not in ScmConfiguration, because it must be available for reading
// from within the code (plugins, etc.), but not for the whole anonymous world outside.
ConfigurationPermissions.read(configuration).check();
return Response.ok(configToDtoMapper.map(configuration)).build();
}
/**
* Modifies the global scm config.
*
* @param configDto new global scm configuration as DTO
*/
@PUT
@Path("")
@Consumes(VndMediaType.CONFIG)
@StatusCodes({
@ResponseCode(code = 201, condition = "update success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to update the global config"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
public Response update(ConfigDto configDto, @Context UriInfo uriInfo) {
// This *could* be moved to ScmConfiguration or ScmConfigurationUtil classes.
// But to where to check? load() or store()? Leave it for now, SCMv1 legacy that can be cleaned up later.
ConfigurationPermissions.write(configuration).check();
ScmConfiguration config = dtoToConfigMapper.map(configDto);
synchronized (ScmConfiguration.class) {
configuration.load(config);
ScmConfigurationUtil.getInstance().store(configuration);
}
return Response.noContent().build();
}
}

View File

@@ -1,23 +1,13 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import com.webcohesion.enunciate.metadata.rs.*;
import sonia.scm.group.Group;
import sonia.scm.group.GroupException;
import sonia.scm.group.GroupManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.io.IOException;

View File

@@ -9,13 +9,7 @@ import sonia.scm.group.GroupManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
public class GroupResource {

View File

@@ -15,6 +15,9 @@ public class MapperModule extends AbstractModule {
bind(GroupToGroupDtoMapper.class).to(Mappers.getMapper(GroupToGroupDtoMapper.class).getClass());
bind(GroupCollectionToDtoMapper.class);
bind(ScmConfigurationToConfigDtoMapper.class).to(Mappers.getMapper(ScmConfigurationToConfigDtoMapper.class).getClass());
bind(ConfigDtoToScmConfigurationMapper.class).to(Mappers.getMapper(ConfigDtoToScmConfigurationMapper.class).getClass());
bind(RepositoryToRepositoryDtoMapper.class).to(Mappers.getMapper(RepositoryToRepositoryDtoMapper.class).getClass());
bind(RepositoryDtoToRepositoryMapper.class).to(Mappers.getMapper(RepositoryDtoToRepositoryMapper.class).getClass());

View File

@@ -0,0 +1,52 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import org.apache.shiro.SecurityUtils;
import sonia.scm.user.User;
import sonia.scm.user.UserException;
import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
@Path(MeResource.ME_PATH_V2)
public class MeResource {
static final String ME_PATH_V2 = "v2/me/";
private final UserToUserDtoMapper userToDtoMapper;
private final IdResourceManagerAdapter<User, UserDto, UserException> adapter;
@Inject
public MeResource(UserToUserDtoMapper userToDtoMapper, UserManager manager) {
this.userToDtoMapper = userToDtoMapper;
this.adapter = new IdResourceManagerAdapter<>(manager, User.class);
}
/**
* Returns the currently logged in user or a 401 if user is not logged in
*/
@GET
@Path("")
@Produces(VndMediaType.USER)
@TypeHint(UserDto.class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response get(@Context Request request, @Context UriInfo uriInfo) {
String id = (String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal();
return adapter.get(id, userToDtoMapper::map);
}
}

View File

@@ -1,23 +1,13 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import com.webcohesion.enunciate.metadata.rs.*;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
public class RepositoryCollectionResource {

View File

@@ -1,10 +1,6 @@
package sonia.scm.api.v2.resources;
import org.mapstruct.AfterMapping;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.*;
import sonia.scm.repository.Repository;
@Mapper

View File

@@ -10,13 +10,7 @@ import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.util.Optional;
import java.util.function.Predicate;

View File

@@ -76,7 +76,7 @@ class ResourceLinks {
return userLinkBuilder.method("getUserResource").parameters(name).method("delete").parameters().href();
}
String update(String name) {
String update(String name) {
return userLinkBuilder.method("getUserResource").parameters(name).method("update").parameters().href();
}
}
@@ -101,6 +101,26 @@ class ResourceLinks {
}
}
ConfigLinks config() {
return new ConfigLinks(uriInfoStore.get());
}
static class ConfigLinks {
private final LinkBuilder configLinkBuilder;
ConfigLinks(UriInfo uriInfo) {
configLinkBuilder = new LinkBuilder(uriInfo, ConfigResource.class);
}
String self() {
return configLinkBuilder.method("get").parameters().href();
}
String update() {
return configLinkBuilder.method("update").parameters().href();
}
}
public RepositoryLinks repository() {
return new RepositoryLinks(uriInfoStore.get());
}

View File

@@ -0,0 +1,34 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.config.ScmConfiguration;
import javax.inject.Inject;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@Mapper
public abstract class ScmConfigurationToConfigDtoMapper {
@Inject
private ResourceLinks resourceLinks;
public abstract ConfigDto map(ScmConfiguration config);
@AfterMapping
void appendLinks(ScmConfiguration config, @MappingTarget ConfigDto target) {
Links.Builder linksBuilder = linkingTo().self(resourceLinks.config().self());
if (ConfigurationPermissions.write(config).isPermitted()) {
linksBuilder.single(link("update", resourceLinks.config().update()));
}
target.add(linksBuilder.build());
}
}

View File

@@ -1,23 +1,13 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import com.webcohesion.enunciate.metadata.rs.*;
import sonia.scm.user.User;
import sonia.scm.user.UserException;
import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.io.IOException;

View File

@@ -9,13 +9,7 @@ import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
public class UserResource {

View File

@@ -1,5 +1,6 @@
package sonia.scm.api.v2.resources;
import com.google.common.annotations.VisibleForTesting;
import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
@@ -21,6 +22,11 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
@Inject
private ResourceLinks resourceLinks;
@VisibleForTesting
void setResourceLinks(ResourceLinks resourceLinks) {
this.resourceLinks = resourceLinks;
}
@AfterMapping
void removePassword(@MappingTarget UserDto target) {
target.setPassword(UserResource.DUMMY_PASSWORT);

View File

@@ -49,6 +49,7 @@ import sonia.scm.security.Role;
*
* @author Sebastian Sdorra
*/
// TODO before releasing v2, delete this filter (we use Permission objects now)
@WebElement(
value = Filters.PATTERN_CONFIG,
morePatterns = {

View File

@@ -44,6 +44,7 @@ import org.apache.shiro.subject.Subject;
import sonia.scm.Priority;
import sonia.scm.SCMContext;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.security.SecurityRequests;
import sonia.scm.web.filter.HttpFilter;
import sonia.scm.web.filter.SecurityHttpServletRequestWrapper;
@@ -69,45 +70,20 @@ public class SecurityFilter extends HttpFilter
@VisibleForTesting
static final String ATTRIBUTE_REMOTE_USER = "principal";
/** Field description */
public static final String URL_AUTHENTICATION = "/api/rest/auth";
private final ScmConfiguration configuration;
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param configuration
*/
@Inject
public SecurityFilter(ScmConfiguration configuration)
{
this.configuration = configuration;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param request
* @param response
* @param chain
*
* @throws IOException
* @throws ServletException
*/
@Override
protected void doFilter(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException
{
String uri =
request.getRequestURI().substring(request.getContextPath().length());
if (!uri.startsWith(URL_AUTHENTICATION))
if (!SecurityRequests.isAuthenticationRequest(request))
{
Subject subject = SecurityUtils.getSubject();
if (hasPermission(subject))
@@ -139,16 +115,6 @@ public class SecurityFilter extends HttpFilter
}
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param subject
*
* @return
*/
protected boolean hasPermission(Subject subject)
{
return ((configuration != null)
@@ -173,8 +139,4 @@ public class SecurityFilter extends HttpFilter
return username;
}
//~--- fields ---------------------------------------------------------------
/** scm configuration */
private final ScmConfiguration configuration;
}

View File

@@ -52,12 +52,7 @@ import sonia.scm.util.CollectionAppender;
import sonia.scm.util.Util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.*;
//~--- JDK imports ------------------------------------------------------------

View File

@@ -1,11 +1,17 @@
package sonia.scm.repository;
import org.apache.shiro.SecurityUtils;
import sonia.scm.plugin.Extension;
/**
* The DefaultNamespaceStrategy returns the username of the currently logged in user as namespace.
* @since 2.0.0
*/
@Extension
public class DefaultNamespaceStrategy implements NamespaceStrategy{
public class DefaultNamespaceStrategy implements NamespaceStrategy {
@Override
public String getNamespace() {
return "42";
return SecurityUtils.getSubject().getPrincipal().toString();
}
}

View File

@@ -42,30 +42,14 @@ import com.google.inject.Singleton;
import org.apache.shiro.concurrent.SubjectAwareExecutorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.ArgumentIsInvalidException;
import sonia.scm.ConfigurationException;
import sonia.scm.HandlerEventType;
import sonia.scm.ManagerDaoAdapter;
import sonia.scm.SCMContextProvider;
import sonia.scm.Type;
import sonia.scm.*;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.security.KeyGenerator;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.CollectionAppender;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
import sonia.scm.util.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
@@ -126,7 +110,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
@Override
public void close() throws IOException {
public void close() {
executorService.shutdown();
for (RepositoryHandler handler : handlerMap.values()) {
@@ -177,40 +161,16 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
getHandler(toDelete).delete(toDelete);
}
/**
* Method description
*
*
* @param repository
*
* @throws IOException
* @throws RepositoryException
*/
@Override
public void importRepository(Repository repository)
throws RepositoryException, IOException {
create(repository, false);
}
/**
* Method description
*
*
* @param context
*/
@Override
public void init(SCMContextProvider context) {
}
/**
* Method description
*
*
* @param repository
*
* @throws IOException
* @throws RepositoryException
*/
@Override
public void modify(Repository repository) throws RepositoryException {
logger.info("modify repository {} of type {}", repository.getName(), repository.getType());
@@ -226,23 +186,13 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
);
}
/**
* Method description
*
*
* @param repository
*
* @throws IOException
* @throws RepositoryException
*/
@Override
public void refresh(Repository repository)
throws RepositoryException {
AssertUtil.assertIsNotNull(repository);
RepositoryPermissions.read(repository).check();
Repository fresh = repositoryDAO.get(repository.getType(),
repository.getName());
Repository fresh = repositoryDAO.get(repository.getNamespaceAndName());
if (fresh != null) {
fresh.copyProperties(repository);
@@ -251,16 +201,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
}
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param id
*
* @return
*/
@Override
public Repository get(String id) {
AssertUtil.assertIsNotEmpty(id);
@@ -276,21 +217,13 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
return repository;
}
/**
* Method description
*
*
* @param type
* @param name
*
* @return
*/
@Override
public Repository get(String type, String name) {
AssertUtil.assertIsNotEmpty(type);
AssertUtil.assertIsNotEmpty(name);
public Repository get(NamespaceAndName namespaceAndName) {
AssertUtil.assertIsNotNull(namespaceAndName);
AssertUtil.assertIsNotEmpty(namespaceAndName.getNamespace());
AssertUtil.assertIsNotEmpty(namespaceAndName.getName());
Repository repository = repositoryDAO.get(type, name);
Repository repository = repositoryDAO.get(namespaceAndName);
if (repository != null) {
RepositoryPermissions.read(repository).check();
@@ -300,14 +233,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
return repository;
}
/**
* Method description
*
*
*
* @param comparator
* @return
*/
@Override
public Collection<Repository> getAll(Comparator<Repository> comparator) {
List<Repository> repositories = Lists.newArrayList();
@@ -330,28 +255,12 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
return repositories;
}
/**
* Method description
*
*
* @return
*/
@Override
public Collection<Repository> getAll() {
return getAll(null);
}
/**
* Method description
*
*
*
* @param comparator
* @param start
* @param limit
*
* @return
*/
@Override
public Collection<Repository> getAll(Comparator<Repository> comparator,
int start, int limit) {
@@ -369,26 +278,11 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
}, start, limit);
}
/**
* Method description
*
*
* @param start
* @param limit
*
* @return
*/
@Override
public Collection<Repository> getAll(int start, int limit) {
return getAll(null, start, limit);
}
/**
* Method description
*
*
* @return
*/
@Override
public Collection<Type> getConfiguredTypes() {
List<Type> validTypes = Lists.newArrayList();
@@ -402,14 +296,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
return validTypes;
}
/**
* Method description
*
*
* @param request
*
* @return
*/
@Override
public Repository getFromRequest(HttpServletRequest request) {
AssertUtil.assertIsNotNull(request);
@@ -417,17 +303,28 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
return getFromUri(HttpUtil.getStrippedURI(request));
}
/**
* Method description
*
*
* @param type
* @param uri
*
* @return
*/
@Override
public Repository getFromTypeAndUri(String type, String uri) {
public Repository getFromUri(String uri) {
AssertUtil.assertIsNotEmpty(uri);
if (uri.startsWith(HttpUtil.SEPARATOR_PATH)) {
uri = uri.substring(1);
}
int typeSeparator = uri.indexOf(HttpUtil.SEPARATOR_PATH);
Repository repository = null;
if (typeSeparator > 0) {
String type = uri.substring(0, typeSeparator);
uri = uri.substring(typeSeparator + 1);
repository = getFromTypeAndUri(type, uri);
}
return repository;
}
private Repository getFromTypeAndUri(String type, String uri) {
if (Strings.isNullOrEmpty(type)) {
throw new ArgumentIsInvalidException("argument type is required");
}
@@ -464,80 +361,21 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
return repository;
}
/**
* Method description
*
*
* @param uri
*
* @return
*/
@Override
public Repository getFromUri(String uri) {
AssertUtil.assertIsNotEmpty(uri);
if (uri.startsWith(HttpUtil.SEPARATOR_PATH)) {
uri = uri.substring(1);
}
int typeSeperator = uri.indexOf(HttpUtil.SEPARATOR_PATH);
Repository repository = null;
if (typeSeperator > 0) {
String type = uri.substring(0, typeSeperator);
uri = uri.substring(typeSeperator + 1);
repository = getFromTypeAndUri(type, uri);
}
return repository;
}
/**
* Method description
*
*
* @param type
*
* @return
*/
@Override
public RepositoryHandler getHandler(String type) {
return handlerMap.get(type);
}
/**
* Method description
*
*
* @return
*/
@Override
public Long getLastModified() {
return repositoryDAO.getLastModified();
}
/**
* Method description
*
*
* @return
*/
@Override
public Collection<Type> getTypes() {
return types;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
*
* @param contextProvider
* @param handler
*/
private void addHandler(SCMContextProvider contextProvider,
RepositoryHandler handler) {
AssertUtil.assertIsNotNull(handler);
@@ -561,19 +399,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
types.add(type);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param repository
*
* @return
*
*
* @throws RepositoryException
*/
private RepositoryHandler getHandler(Repository repository)
throws RepositoryException {
String type = repository.getType();

View File

@@ -33,7 +33,7 @@ package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.eventbus.Subscribe;
import com.github.legman.Subscribe;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -32,14 +32,15 @@
package sonia.scm.repository;
import com.google.common.collect.Maps;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.Util;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.Util;
import javax.inject.Inject;
import java.util.Map;
import java.util.Set;
/**
* RepositoryMatcher is able to check if a repository matches the requested path.
@@ -83,9 +84,24 @@ public final class RepositoryMatcher {
}
private boolean isPathMatching(Repository repository, String path) {
return getPathMatcherForType(repository.getType()).isPathMatching(repository, path);
String namespace = extractNamespace(path);
String remainingPath = path.substring(namespace.length() + 1);
return getPathMatcherForType(repository.getType()).isPathMatching(repository, remainingPath);
}
private String extractNamespace(String path) {
if (path.startsWith(HttpUtil.SEPARATOR_PATH)) {
path = path.substring(1);
}
int namespaceSeparator = path.indexOf(HttpUtil.SEPARATOR_PATH);
if (namespaceSeparator > 0) {
return path.substring(0, namespaceSeparator);
}
throw new IllegalArgumentException("no namespace in path " + path);
}
private RepositoryPathMatcher getPathMatcherForType(String type) {
RepositoryPathMatcher pathMatcher = pathMatchers.get(type);
if (pathMatcher == null) {

View File

@@ -30,8 +30,8 @@
*/
package sonia.scm.security;
import com.github.legman.Subscribe;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.eventbus.Subscribe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.EagerSingleton;

View File

@@ -0,0 +1,24 @@
package sonia.scm.security;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Pattern;
/**
* Created by masuewer on 04.07.18.
*/
public final class SecurityRequests {
private static final Pattern URI_LOGIN_PATTERN = Pattern.compile("/api/rest(?:/v2)?/auth/access_token");
private SecurityRequests() {}
public static boolean isAuthenticationRequest(HttpServletRequest request) {
String uri = request.getRequestURI().substring(request.getContextPath().length());
return isAuthenticationRequest(uri);
}
public static boolean isAuthenticationRequest(String uri) {
return URI_LOGIN_PATTERN.matcher(uri).matches();
}
}

View File

@@ -1,81 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.url;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import com.google.inject.Provider;
import sonia.scm.config.ScmConfiguration;
/**
*
* @author Sebastian Sdorra
*/
public class RestJsonUrlProvider implements Provider<UrlProvider>
{
/**
* Constructs ...
*
*
* @param configuration
*/
@Inject
public RestJsonUrlProvider(ScmConfiguration configuration)
{
this.configuration = configuration;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public UrlProvider get()
{
return UrlProviderFactory.createUrlProvider(configuration.getBaseUrl(),
UrlProviderFactory.TYPE_RESTAPI_JSON);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private ScmConfiguration configuration;
}

View File

@@ -1,81 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.url;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import com.google.inject.Provider;
import sonia.scm.config.ScmConfiguration;
/**
*
* @author Sebastian Sdorra
*/
public class RestXmlUrlProvider implements Provider<UrlProvider>
{
/**
* Constructs ...
*
*
* @param configuration
*/
@Inject
public RestXmlUrlProvider(ScmConfiguration configuration)
{
this.configuration = configuration;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public UrlProvider get()
{
return UrlProviderFactory.createUrlProvider(configuration.getBaseUrl(),
UrlProviderFactory.TYPE_RESTAPI_XML);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private ScmConfiguration configuration;
}

View File

@@ -1,81 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.url;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import com.google.inject.Provider;
import sonia.scm.config.ScmConfiguration;
/**
*
* @author Sebastian Sdorra
*/
public class WebUIUrlProvider implements Provider<UrlProvider>
{
/**
* Constructs ...
*
*
* @param configuration
*/
@Inject
public WebUIUrlProvider(ScmConfiguration configuration)
{
this.configuration = configuration;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public UrlProvider get()
{
return UrlProviderFactory.createUrlProvider(configuration.getBaseUrl(),
UrlProviderFactory.TYPE_WUI);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private ScmConfiguration configuration;
}

View File

@@ -40,6 +40,7 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.EagerSingleton;
import sonia.scm.HandlerEventType;
import sonia.scm.ManagerDaoAdapter;
import sonia.scm.SCMContextProvider;
@@ -67,7 +68,7 @@ import java.util.List;
*
* @author Sebastian Sdorra
*/
@Singleton
@Singleton @EagerSingleton
public class DefaultUserManager extends AbstractUserManager
{

View File

@@ -36,24 +36,22 @@ package sonia.scm.web.security;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import sonia.scm.Priority;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.filter.Filters;
import sonia.scm.filter.WebElement;
import sonia.scm.web.filter.AuthenticationFilter;
import sonia.scm.security.SecurityRequests;
import sonia.scm.web.WebTokenGenerator;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.util.Set;
import sonia.scm.web.filter.AuthenticationFilter;
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;
//~--- JDK imports ------------------------------------------------------------
/**
* Filter to handle authentication for the rest api of SCM-Manager.
@@ -66,9 +64,6 @@ import javax.servlet.http.HttpServletResponse;
public class ApiAuthenticationFilter extends AuthenticationFilter
{
/** login uri */
public static final String URI_LOGIN = "/api/rest/auth/access_token";
//~--- constructors ---------------------------------------------------------
/**
@@ -104,7 +99,7 @@ public class ApiAuthenticationFilter extends AuthenticationFilter
throws IOException, ServletException
{
// skip filter on login resource
if (request.getRequestURI().contains(URI_LOGIN))
if (SecurityRequests.isAuthenticationRequest(request))
{
chain.doFilter(request, response);
}

View File

@@ -99,4 +99,4 @@
<appender-ref ref="STDOUT" />
</root>
</configuration>
</configuration>

View File

@@ -93,7 +93,7 @@
<ul>
{{#repositories}}
<li>
<a href="{{url}}">{{name}}</a> (<a href="{{detailUrl}}">Details</a>, <a href="{{commitUrl}}">Commits</a>, <a href="{{sourceUrl}}">Source</a>)
<a href="{{url}}">{{name}}</a>
</li>
{{/repositories}}
</ul>

View File

@@ -0,0 +1,232 @@
package sonia.scm.api.v2.resources;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockDispatcherFactory;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.security.AccessToken;
import sonia.scm.security.AccessTokenBuilder;
import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.security.AccessTokenCookieIssuer;
import sonia.scm.user.User;
import sonia.scm.user.UserException;
import sonia.scm.user.UserManager;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
@SubjectAware(
configuration = "classpath:sonia/scm/repository/shiro.ini"
)
@RunWith(MockitoJUnitRunner.class)
public class AuthenticationResourceTest {
@Rule
public ShiroRule shiro = new ShiroRule();
private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
@Mock
private AccessTokenBuilderFactory accessTokenBuilderFactory;
@Mock
private AccessTokenBuilder accessTokenBuilder;
private AccessTokenCookieIssuer cookieIssuer = new AccessTokenCookieIssuer(mock(ScmConfiguration.class));
private static final String AUTH_JSON_TRILLIAN = "{\n" +
"\t\"cookie\": true,\n" +
"\t\"grant_type\": \"password\",\n" +
"\t\"username\": \"trillian\",\n" +
"\t\"password\": \"secret\"\n" +
"}";
private static final String AUTH_FORMENCODED_TRILLIAN = "cookie=true&grant_type=password&username=trillian&password=secret";
private static final String AUTH_JSON_TRILLIAN_WRONG_PW = "{\n" +
"\t\"cookie\": true,\n" +
"\t\"grant_type\": \"password\",\n" +
"\t\"username\": \"trillian\",\n" +
"\t\"password\": \"justWrong\"\n" +
"}";
private static final String AUTH_JSON_NOT_EXISTING_USER = "{\n" +
"\t\"cookie\": true,\n" +
"\t\"grant_type\": \"password\",\n" +
"\t\"username\": \"iDoNotExist\",\n" +
"\t\"password\": \"doesNotMatter\"\n" +
"}";
private static final String AUTH_JSON_WITHOUT_USERNAME = String.join("\n",
"{",
"\"grant_type\": \"password\",",
"\"password\": \"tricia123\"",
"}"
);
private static final String AUTH_JSON_WITHOUT_PASSWORD = String.join("\n",
"{",
"\"grant_type\": \"password\",",
"\"username\": \"trillian\"",
"}"
);
private static final String AUTH_JSON_WITHOUT_GRANT_TYPE = String.join("\n",
"{",
"\"username\": \"trillian\",",
"\"password\": \"tricia123\"",
"}"
);
private static final String AUTH_JSON_WITH_INVALID_GRANT_TYPE = String.join("\n",
"{",
"\"grant_type\": \"el speciale\",",
"\"username\": \"trillian\",",
"\"password\": \"tricia123\"",
"}"
);
@Before
public void prepareEnvironment() {
AuthenticationResource authenticationResource = new AuthenticationResource(accessTokenBuilderFactory, cookieIssuer);
dispatcher.getRegistry().addSingletonResource(authenticationResource);
AccessToken accessToken = mock(AccessToken.class);
when(accessToken.getExpiration()).thenReturn(new Date(Long.MAX_VALUE));
when(accessTokenBuilder.build()).thenReturn(accessToken);
when(accessTokenBuilderFactory.create()).thenReturn(accessTokenBuilder);
HttpServletRequest servletRequest = mock(HttpServletRequest.class);
ResteasyProviderFactory.getContextDataMap().put(HttpServletRequest.class, servletRequest);
HttpServletResponse servletResponse = mock(HttpServletResponse.class);
ResteasyProviderFactory.getContextDataMap().put(HttpServletResponse.class, servletResponse);
}
@Test
public void shouldAuthCorrectly() throws URISyntaxException {
MockHttpRequest request = getMockHttpRequest(AUTH_JSON_TRILLIAN);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
}
@Test
public void shouldAuthCorrectlyWithFormencodedData() throws URISyntaxException {
MockHttpRequest request = getMockHttpRequestUrlEncoded(AUTH_FORMENCODED_TRILLIAN);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
}
@Test
public void shouldNotAuthUserWithWrongPassword() throws URISyntaxException {
MockHttpRequest request = getMockHttpRequest(AUTH_JSON_TRILLIAN_WRONG_PW);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_UNAUTHORIZED, response.getStatus());
}
@Test
public void shouldNotAuthNonexistingUser() throws URISyntaxException {
MockHttpRequest request = getMockHttpRequest(AUTH_JSON_NOT_EXISTING_USER);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_UNAUTHORIZED, response.getStatus());
}
@Test
public void shouldReturnBadStatusIfPasswordParameterIsMissing() throws URISyntaxException {
shouldReturnBadRequest(AUTH_JSON_WITHOUT_USERNAME);
}
@Test
public void shouldReturnBadStatusIfUsernameParameterIsMissing() throws URISyntaxException {
shouldReturnBadRequest(AUTH_JSON_WITHOUT_PASSWORD);
}
@Test
public void shouldReturnBadStatusIfGrantTypeParameterIsMissing() throws URISyntaxException {
shouldReturnBadRequest(AUTH_JSON_WITHOUT_GRANT_TYPE);
}
@Test
public void shouldReturnBadStatusIfGrantTypeParameterIsInvalid() throws URISyntaxException {
shouldReturnBadRequest(AUTH_JSON_WITH_INVALID_GRANT_TYPE);
}
@Test
@SubjectAware(username = "trillian", password = "secret")
public void shouldSuccessfullyLogoutUser() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest.delete("/" + AuthenticationResource.PATH + "/access_token");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
}
private void shouldReturnBadRequest(String requestBody) throws URISyntaxException {
MockHttpRequest request = getMockHttpRequest(requestBody);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
}
private MockHttpRequest getMockHttpRequest(String jsonPayload) throws URISyntaxException {
MockHttpRequest request = MockHttpRequest.post("/" + AuthenticationResource.PATH + "/access_token");
request.content(jsonPayload.getBytes());
request.contentType(MediaType.APPLICATION_JSON_TYPE);
return request;
}
private MockHttpRequest getMockHttpRequestUrlEncoded(String payload) throws URISyntaxException {
MockHttpRequest request = MockHttpRequest.post("/" + AuthenticationResource.PATH + "/access_token");
request.content(payload.getBytes());
request.contentType(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
return request;
}
}

View File

@@ -0,0 +1,81 @@
package sonia.scm.api.v2.resources;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.internal.util.collections.Sets;
import sonia.scm.config.ScmConfiguration;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.MockitoAnnotations.initMocks;
public class ConfigDtoToScmConfigurationMapperTest {
@InjectMocks
private ConfigDtoToScmConfigurationMapperImpl mapper;
private String[] expectedUsers = { "trillian", "arthur" };
private String[] expectedGroups = { "admin", "plebs" };
private String[] expectedExcludes = { "ex", "clude" };
@Before
public void init() {
initMocks(this);
}
@Test
public void shouldMapFields() {
ConfigDto dto = createDefaultDto();
ScmConfiguration config = mapper.map(dto);
assertEquals("prPw" , config.getProxyPassword());
assertEquals(42 , config.getProxyPort());
assertEquals("srvr" , config.getProxyServer());
assertEquals("user" , config.getProxyUser());
assertTrue(config.isEnableProxy());
assertEquals("realm" , config.getRealmDescription());
assertTrue(config.isEnableRepositoryArchive());
assertTrue(config.isDisableGroupingGrid());
assertEquals("yyyy" , config.getDateFormat());
assertTrue(config.isAnonymousAccessEnabled());
assertTrue("adminGroups", config.getAdminGroups().containsAll(Arrays.asList(expectedGroups)));
assertTrue("adminUsers", config.getAdminUsers().containsAll(Arrays.asList(expectedUsers)));
assertEquals("baseurl" , config.getBaseUrl());
assertTrue(config.isForceBaseUrl());
assertEquals(41 , config.getLoginAttemptLimit());
assertTrue("proxyExcludes", config.getProxyExcludes().containsAll(Arrays.asList(expectedExcludes)));
assertTrue(config.isSkipFailedAuthenticators());
assertEquals("https://plug.ins" , config.getPluginUrl());
assertEquals(40 , config.getLoginAttemptLimitTimeout());
assertTrue(config.isEnabledXsrfProtection());
}
private ConfigDto createDefaultDto() {
ConfigDto configDto = new ConfigDto();
configDto.setProxyPassword("prPw");
configDto.setProxyPort(42);
configDto.setProxyServer("srvr");
configDto.setProxyUser("user");
configDto.setEnableProxy(true);
configDto.setRealmDescription("realm");
configDto.setEnableRepositoryArchive(true);
configDto.setDisableGroupingGrid(true);
configDto.setDateFormat("yyyy");
configDto.setAnonymousAccessEnabled(true);
configDto.setAdminGroups(Sets.newSet(expectedGroups));
configDto.setAdminUsers(Sets.newSet(expectedUsers));
configDto.setBaseUrl("baseurl");
configDto.setForceBaseUrl(true);
configDto.setLoginAttemptLimit(41);
configDto.setProxyExcludes(Sets.newSet(expectedExcludes));
configDto.setSkipFailedAuthenticators(true);
configDto.setPluginUrl("https://plug.ins");
configDto.setLoginAttemptLimitTimeout(40);
configDto.setEnabledXsrfProtection(true);
return configDto;
}
}

View File

@@ -0,0 +1,127 @@
package sonia.scm.api.v2.resources;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import com.google.common.io.Resources;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockDispatcherFactory;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.InjectMocks;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.web.VndMediaType;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.MockitoAnnotations.initMocks;
@SubjectAware(
configuration = "classpath:sonia/scm/configuration/shiro.ini",
password = "secret"
)
public class ConfigResourceTest {
@Rule
public ShiroRule shiro = new ShiroRule();
@Rule
public ExpectedException thrown = ExpectedException.none();
private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
private final URI baseUri = URI.create("/");
@SuppressWarnings("unused") // Is injected
private ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@InjectMocks
private ConfigDtoToScmConfigurationMapperImpl dtoToConfigMapper;
@InjectMocks
private ScmConfigurationToConfigDtoMapperImpl configToDtoMapper;
@Before
public void prepareEnvironment() {
initMocks(this);
ConfigResource configResource = new ConfigResource(dtoToConfigMapper, configToDtoMapper, createConfiguration());
dispatcher.getRegistry().addSingletonResource(configResource);
}
@Test
@SubjectAware(username = "readOnly")
public void shouldGetGlobalConfig() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
assertTrue(response.getContentAsString().contains("\"proxyPassword\":\"heartOfGold\""));
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config"));
assertFalse("Update link present", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config"));
}
@Test
@SubjectAware(username = "writeOnly")
public void shouldGetConfigOnlyWhenAuthorized() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2);
MockHttpResponse response = new MockHttpResponse();
thrown.expectMessage("Subject does not have permission [configuration:read:global]");
dispatcher.invoke(request, response);
}
@Test
@SubjectAware(username = "readWrite")
public void shouldUpdateConfig() throws URISyntaxException, IOException {
URL url = Resources.getResource("sonia/scm/api/v2/config-test-update.json");
byte[] configJson = Resources.toByteArray(url);
MockHttpRequest request = MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2)
.contentType(VndMediaType.CONFIG)
.content(configJson);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2);
response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
assertTrue(response.getContentAsString().contains("\"proxyPassword\":\"newPassword\""));
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config"));
assertTrue("link not found", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config"));
}
@Test
@SubjectAware(username = "readOnly")
public void shouldUpdateConfigOnlyWhenAuthorized() throws URISyntaxException, IOException {
URL url = Resources.getResource("sonia/scm/api/v2/config-test-update.json");
byte[] configJson = Resources.toByteArray(url);
MockHttpRequest request = MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2)
.contentType(VndMediaType.CONFIG)
.content(configJson);
MockHttpResponse response = new MockHttpResponse();
thrown.expectMessage("Subject does not have permission [configuration:write:global]");
dispatcher.invoke(request, response);
}
private static ScmConfiguration createConfiguration() {
ScmConfiguration scmConfiguration = new ScmConfiguration();
scmConfiguration.setProxyPassword("heartOfGold");
return scmConfiguration;
}
}

View File

@@ -27,9 +27,7 @@ import java.net.URL;
import java.util.Collections;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -111,8 +109,6 @@ public class GroupRootResourceTest {
Group group = createDummyGroup();
when(groupManager.get("admin")).thenReturn(group);
Group updatedGroup = createDummyGroup();
updatedGroup.setDescription("Updated description");
MockHttpRequest request = MockHttpRequest
.put("/" + GroupRootResource.GROUPS_PATH_V2 + "admin")
@@ -134,9 +130,6 @@ public class GroupRootResourceTest {
URL url = Resources.getResource("sonia/scm/api/v2/group-test-update.json");
byte[] groupJson = Resources.toByteArray(url);
Group updatedGroup = createDummyGroup();
updatedGroup.setDescription("Updated description");
MockHttpRequest request = MockHttpRequest
.put("/" + GroupRootResource.GROUPS_PATH_V2 + "idontexist")
.contentType(VndMediaType.GROUP)

View File

@@ -0,0 +1,104 @@
package sonia.scm.api.v2.resources;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import com.google.common.io.Resources;
import org.apache.shiro.authc.credential.PasswordService;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockDispatcherFactory;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import sonia.scm.PageResult;
import sonia.scm.user.User;
import sonia.scm.user.UserException;
import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.initMocks;
@SubjectAware(
configuration = "classpath:sonia/scm/repository/shiro.ini"
)
public class MeResourceTest {
@Rule
public ShiroRule shiro = new ShiroRule();
private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("/"));
@Mock
private UriInfo uriInfo;
@Mock
private UriInfoStore uriInfoStore;
@Mock
private UserManager userManager;
@InjectMocks
private UserToUserDtoMapperImpl userToDtoMapper;
private ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
@Before
public void prepareEnvironment() throws IOException, UserException {
initMocks(this);
createDummyUser("trillian");
when(userManager.create(userCaptor.capture())).thenAnswer(invocation -> invocation.getArguments()[0]);
doNothing().when(userManager).modify(userCaptor.capture());
doNothing().when(userManager).delete(userCaptor.capture());
userToDtoMapper.setResourceLinks(resourceLinks);
MeResource meResource = new MeResource(userToDtoMapper, userManager);
dispatcher.getRegistry().addSingletonResource(meResource);
when(uriInfo.getBaseUri()).thenReturn(URI.create("/"));
when(uriInfoStore.get()).thenReturn(uriInfo);
}
@Test
@SubjectAware(username = "trillian", password = "secret")
public void shouldReturnCurrentlyAuthenticatedUser() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest.get("/" + MeResource.ME_PATH_V2);
request.accept(VndMediaType.USER);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
assertTrue(response.getContentAsString().contains("\"name\":\"trillian\""));
assertTrue(response.getContentAsString().contains("\"password\":\"__dummypassword__\""));
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/users/trillian\"}"));
assertTrue(response.getContentAsString().contains("\"delete\":{\"href\":\"/v2/users/trillian\"}"));
}
private User createDummyUser(String name) {
User user = new User();
user.setName(name);
user.setPassword("secret");
user.setCreationDate(System.currentTimeMillis());
when(userManager.get(name)).thenReturn(user);
return user;
}
}

View File

@@ -27,19 +27,14 @@ import java.net.URL;
import static java.util.Collections.singletonList;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static javax.servlet.http.HttpServletResponse.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.initMocks;
@SubjectAware(

View File

@@ -24,6 +24,7 @@ public class ResourceLinksMock {
when(resourceLinks.changesetCollection()).thenReturn(new ResourceLinks.ChangesetCollectionLinks(uriInfo));
when(resourceLinks.sourceCollection()).thenReturn(new ResourceLinks.SourceCollectionLinks(uriInfo));
when(resourceLinks.permissionCollection()).thenReturn(new ResourceLinks.PermissionCollectionLinks(uriInfo));
when(resourceLinks.config()).thenReturn(new ResourceLinks.ConfigLinks(uriInfo));
return resourceLinks;
}
}

View File

@@ -132,6 +132,18 @@ public class ResourceLinksTest {
assertEquals(BASE_URL + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/sources/", url);
}
@Test
public void shouldCreateCorrectConfigSelfUrl() {
String url = resourceLinks.config().self();
assertEquals(BASE_URL + ConfigResource.CONFIG_PATH_V2, url);
}
@Test
public void shouldCreateCorrectConfigUpdateUrl() {
String url = resourceLinks.config().update();
assertEquals(BASE_URL + ConfigResource.CONFIG_PATH_V2, url);
}
@Before
public void initUriInfo() {
initMocks(this);

View File

@@ -0,0 +1,126 @@
package sonia.scm.api.v2.resources;
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.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.internal.util.collections.Sets;
import sonia.scm.config.ScmConfiguration;
import java.net.URI;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
public class ScmConfigurationToConfigDtoMapperTest {
private URI baseUri = URI.create("http://example.com/base/");
private String[] expectedUsers = { "trillian", "arthur" };
private String[] expectedGroups = { "admin", "plebs" };
private String[] expectedExcludes = { "ex", "clude" };
@SuppressWarnings("unused") // Is injected
private ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@InjectMocks
private ScmConfigurationToConfigDtoMapperImpl mapper;
private final Subject subject = mock(Subject.class);
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
private URI expectedBaseUri;
@Before
public void init() {
initMocks(this);
expectedBaseUri = baseUri.resolve(ConfigResource.CONFIG_PATH_V2);
subjectThreadState.bind();
ThreadContext.bind(subject);
}
@After
public void unbindSubject() {
ThreadContext.unbindSubject();
}
@Test
public void shouldMapFields() {
ScmConfiguration config = createConfiguration();
when(subject.isPermitted("configuration:write:global")).thenReturn(true);
ConfigDto dto = mapper.map(config);
assertEquals("heartOfGold" , dto.getProxyPassword());
assertEquals(1234 , dto.getProxyPort());
assertEquals("proxyserver" , dto.getProxyServer());
assertEquals("trillian" , dto.getProxyUser());
assertTrue(dto.isEnableProxy());
assertEquals("description" , dto.getRealmDescription());
assertTrue(dto.isEnableRepositoryArchive());
assertTrue(dto.isDisableGroupingGrid());
assertEquals("dd" , dto.getDateFormat());
assertTrue(dto.isAnonymousAccessEnabled());
assertTrue("adminGroups", dto.getAdminGroups().containsAll(Arrays.asList(expectedGroups)));
assertTrue("adminUsers", dto.getAdminUsers().containsAll(Arrays.asList(expectedUsers)));
assertEquals("baseurl" , dto.getBaseUrl());
assertTrue(dto.isForceBaseUrl());
assertEquals(1 , dto.getLoginAttemptLimit());
assertTrue("proxyExcludes", dto.getProxyExcludes().containsAll(Arrays.asList(expectedExcludes)));
assertTrue(dto.isSkipFailedAuthenticators());
assertEquals("pluginurl" , dto.getPluginUrl());
assertEquals(2 , dto.getLoginAttemptLimitTimeout());
assertTrue(dto.isEnabledXsrfProtection());
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref());
}
@Test
public void shouldMapFieldsWithoutUpdate() {
ScmConfiguration config = createConfiguration();
when(subject.hasRole("configuration:write:global")).thenReturn(false);
ConfigDto dto = mapper.map(config);
assertEquals("baseurl", dto.getBaseUrl());
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
assertFalse(dto.getLinks().hasLink("update"));
}
private ScmConfiguration createConfiguration() {
ScmConfiguration config = new ScmConfiguration();
config.setProxyPassword("heartOfGold");
config.setProxyPort(1234);
config.setProxyServer("proxyserver");
config.setProxyUser("trillian");
config.setEnableProxy(true);
config.setRealmDescription("description");
config.setEnableRepositoryArchive(true);
config.setDisableGroupingGrid(true);
config.setDateFormat("dd");
config.setAnonymousAccessEnabled(true);
config.setAdminGroups(Sets.newSet(expectedGroups));
config.setAdminUsers(Sets.newSet(expectedUsers));
config.setBaseUrl("baseurl");
config.setForceBaseUrl(true);
config.setLoginAttemptLimit(1);
config.setProxyExcludes(Sets.newSet(expectedExcludes));
config.setSkipFailedAuthenticators(true);
config.setPluginUrl("pluginurl");
config.setLoginAttemptLimitTimeout(2);
config.setEnabledXsrfProtection(true);
return config;
}
}

View File

@@ -18,9 +18,7 @@ import java.util.Arrays;
import java.util.List;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import static sonia.scm.PageResult.createPage;

View File

@@ -27,15 +27,10 @@ import java.net.URISyntaxException;
import java.net.URL;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.initMocks;
@SubjectAware(

View File

@@ -100,14 +100,29 @@ public class SecurityFilterTest {
}
/**
* Tests filter on authentication endpoint.
*
* Tests filter on authentication endpoint v1.
*
* @throws IOException
* @throws ServletException
*/
@Test
public void testDoOnAuthenticationUrl() throws IOException, ServletException {
when(request.getRequestURI()).thenReturn("/scm/api/rest/authentication");
public void testDoOnAuthenticationUrlV1() throws IOException, ServletException {
checkIfAuthenticationUrlIsPassedThrough("/scm/api/rest/auth/access_token");
}
/**
* Tests filter on authentication endpoint v2.
*
* @throws IOException
* @throws ServletException
*/
@Test
public void testDoOnAuthenticationUrlV2() throws IOException, ServletException {
checkIfAuthenticationUrlIsPassedThrough("/scm/api/rest/v2/auth/access_token");
}
private void checkIfAuthenticationUrlIsPassedThrough(String uri) throws IOException, ServletException {
when(request.getRequestURI()).thenReturn(uri);
securityFilter.doFilter(request, response, chain);
verify(request, never()).setAttribute(Mockito.anyString(), Mockito.any());
verify(chain).doFilter(request, response);
@@ -235,4 +250,4 @@ public class SecurityFilterTest {
}
}
}

View File

@@ -0,0 +1,24 @@
package sonia.scm.repository;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import org.junit.Rule;
import org.junit.Test;
import static org.junit.Assert.*;
@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini")
public class DefaultNamespaceStrategyTest {
@Rule
public ShiroRule shiroRule = new ShiroRule();
private DefaultNamespaceStrategy namespaceStrategy = new DefaultNamespaceStrategy();
@Test
@SubjectAware(username = "trillian", password = "secret")
public void testNamespaceStrategy() {
assertEquals("trillian", namespaceStrategy.getNamespace());
}
}

View File

@@ -96,8 +96,6 @@ public class DefaultRepositoryManagerPerfTest {
private final KeyGenerator keyGenerator = new DefaultKeyGenerator();
private final NamespaceStrategy namespaceStrategy = new DefaultNamespaceStrategy();
@Mock
private RepositoryHandler repositoryHandler;
@@ -114,7 +112,7 @@ public class DefaultRepositoryManagerPerfTest {
when(repositoryHandler.getType()).thenReturn(new Type(REPOSITORY_TYPE, REPOSITORY_TYPE));
Set<RepositoryHandler> handlerSet = ImmutableSet.of(repositoryHandler);
RepositoryMatcher repositoryMatcher = new RepositoryMatcher(Collections.<RepositoryPathMatcher>emptySet());
NamespaceStrategy namespaceStrategy = mock(NamespaceStrategy.class);
repositoryManager = new DefaultRepositoryManager(
configuration,
contextProvider,
@@ -132,10 +130,7 @@ public class DefaultRepositoryManagerPerfTest {
ThreadContext.bind(securityManager);
}
/**
* Tear down test objects.
*/
@After
public void tearDown(){
ThreadContext.unbindSecurityManager();
@@ -188,8 +183,8 @@ private long calculateAverage(List<Long> times) {
when(repositoryDAO.getAll()).thenReturn(repositories.values());
}
private Repository createTestRepository(int number){
Repository repository = new Repository(keyGenerator.createKey(), REPOSITORY_TYPE, "repo-" + number);
private Repository createTestRepository(int number) {
Repository repository = new Repository(keyGenerator.createKey(), REPOSITORY_TYPE, "namespace", "repo-" + number);
repository.getPermissions().add(new Permission("trillian", PermissionType.READ));
return repository;
}

View File

@@ -41,12 +41,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.invocation.InvocationOnMock;
import sonia.scm.HandlerEventType;
import sonia.scm.Manager;
import sonia.scm.ManagerTestBase;
import sonia.scm.ModelObject;
import sonia.scm.Type;
import sonia.scm.TypedObject;
import sonia.scm.*;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.api.HookContext;
@@ -59,24 +54,12 @@ import sonia.scm.security.KeyGenerator;
import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.store.JAXBConfigurationStoreFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;
import java.util.*;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
//~--- JDK imports ------------------------------------------------------------
@@ -101,9 +84,8 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
private ScmConfiguration configuration;
/**
* Tests {@link RepositoryManager#create(TypedObject)}.
*/
private String mockedNamespace = "default_namespace";
@Test
public void testCreate() throws RepositoryException {
Repository heartOfGold = createTestRepository();
@@ -113,9 +95,6 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
assertRepositoriesEquals(dbRepo, heartOfGold);
}
/**
* Tests {@link RepositoryManager#create(TypedObject)} without the required permissions.
*/
@SubjectAware(
username = "unpriv"
)
@@ -124,26 +103,17 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
createTestRepository();
}
/**
* Tests {@link RepositoryManager#create(TypedObject)} with a already existing repository.
*/
@Test(expected = RepositoryAlreadyExistsException.class)
public void testCreateExisting() throws RepositoryException {
createTestRepository();
createTestRepository();
}
/**
* Tests {@link RepositoryManager#delete(TypedObject)}.
*/
@Test
public void testDelete() throws RepositoryException {
delete(manager, createTestRepository());
}
/**
* Tests {@link RepositoryManager#delete(TypedObject)} without the required permissions.
*/
@SubjectAware(
username = "unpriv"
)
@@ -152,27 +122,17 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
delete(manager, createTestRepository());
}
/**
* Tests {@link RepositoryManager#delete(TypedObject)} with a non archived repository and with
* enabled archive mode.
*/
@Test(expected = RepositoryIsNotArchivedException.class)
public void testDeleteNonArchived() throws RepositoryException {
configuration.setEnableRepositoryArchive(true);
delete(manager, createTestRepository());
}
/**
* Tests {@link RepositoryManager#delete(TypedObject)} with a non existing repository.
*/
@Test(expected = RepositoryNotFoundException.class)
public void testDeleteNotFound() throws RepositoryException {
manager.delete(createRepositoryWithId());
}
/**
* Tests {@link RepositoryManager#delete(TypedObject)} with enabled archive mode.
*/
@Test
public void testDeleteWithEnabledArchive()
throws RepositoryException {
@@ -184,9 +144,6 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
delete(drm, repository);
}
/**
* Tests {@link RepositoryManager#get(java.lang.String)} .
*/
@Test
public void testGet() throws RepositoryException {
Repository heartOfGold = createTestRepository();
@@ -202,9 +159,6 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
assertEquals(description, heartOfGold.getDescription());
}
/**
* Tests {@link RepositoryManager#get(java.lang.String)} without required privileges.
*/
@Test
@SubjectAware(
username = "crato"
@@ -217,9 +171,6 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
manager.get(heartOfGold.getId());
}
/**
* Tests {@link RepositoryManager#getAll()}.
*/
@Test
public void testGetAll() throws RepositoryException {
Repository heartOfGold = createTestRepository();
@@ -256,13 +207,10 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
heartOfGold.getDescription().equals(heartReference.getDescription()));
}
/**
* Tests {@link RepositoryManager#getAll()} with permission for 2 of 3 repositories.
*/
@Test
@SuppressWarnings("unchecked")
@SubjectAware(username = "dent")
public void testGetAllWithPermissions() throws RepositoryException {
public void testGetAllWithPermissionsForTwoOrThreeRepos() throws RepositoryException {
// mock key generator
KeyGenerator keyGenerator = mock(KeyGenerator.class);
Stack<String> keys = new Stack<>();
@@ -302,9 +250,6 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
);
}
/**
* Tests repository manager events.
*/
@Test
public void testEvents() throws RepositoryException {
RepositoryManager repoManager = createRepositoryManager(false);
@@ -336,9 +281,6 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
assertSame(HandlerEventType.DELETE, listener.postEvent);
}
/**
* Tests {@link RepositoryManager#modify(TypedObject)}.
*/
@Test
public void testModify() throws RepositoryException {
Repository heartOfGold = createTestRepository();
@@ -351,11 +293,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
assertNotNull(hearReference);
assertEquals(hearReference.getDescription(), "prototype ship");
}
/**
* Tests {@link RepositoryManager#modify(TypedObject)} without
* the required permissions.
*/
@Test
@SubjectAware(username = "crato")
public void testModifyWithoutRequiredPermissions() throws RepositoryException {
@@ -367,18 +305,11 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
manager.modify(heartOfGold);
}
/**
* Tests {@link RepositoryManager#modify(TypedObject)} with a non
* existing repository.
*/
@Test(expected = RepositoryNotFoundException.class)
public void testModifyNotFound() throws RepositoryException {
manager.modify(createRepositoryWithId());
}
/**
* Tests {@link RepositoryManager#refresh(ModelObject)}.
*/
@Test
public void testRefresh() throws RepositoryException {
Repository heartOfGold = createTestRepository();
@@ -389,10 +320,6 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
assertEquals(description, heartOfGold.getDescription());
}
/**
* Tests {@link RepositoryManager#refresh(ModelObject)} without
* required permissions.
*/
@Test
@SubjectAware(username = "crato")
public void testRefreshWithoutRequiredPermissions() throws RepositoryException {
@@ -404,18 +331,11 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
manager.refresh(heartOfGold);
}
/**
* Tests {@link RepositoryManager#refresh(ModelObject)} with a non existing
* repository.
*/
@Test(expected = RepositoryNotFoundException.class)
public void testRefreshNotFound() throws RepositoryException {
manager.refresh(createRepositoryWithId());
}
/**
* Tests repository hooks.
*/
@Test
public void testRepositoryHook() throws RepositoryException {
CountingReceiveHook hook = new CountingReceiveHook();
@@ -436,25 +356,96 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
assertEquals(2, hook.eventsReceived);
}
/**
* Tests {@link RepositoryManager#getFromTypeAndUri(String, String)}.
*/
@Test
public void getRepositoryFromRequestUriTest() throws RepositoryException {
public void testNamespaceSet() throws Exception {
RepositoryManager repoManager = createRepositoryManager(false);
Repository repository = spy(createTestRepository());
repository.setName("Testrepo");
((DefaultRepositoryManager) repoManager).create(repository);
assertEquals("default_namespace", repository.getNamespace());
}
@Test
public void getRepositoryFromRequestUri_withoutLeadingSlash() throws RepositoryException {
RepositoryManager m = createManager();
m.init(contextProvider);
createRepository(m, new Repository("1", "hg", "scm"));
createRepository(m, new Repository("2", "hg", "scm-test"));
createRepository(m, new Repository("3", "git", "project1/test-1"));
createRepository(m, new Repository("4", "git", "project1/test-2"));
assertEquals("scm", m.getFromUri("hg/scm").getName());
assertEquals("scm-test", m.getFromUri("hg/scm-test").getName());
assertEquals("scm-test", m.getFromUri("/hg/scm-test").getName());
assertEquals("project1/test-1", m.getFromUri("/git/project1/test-1").getName());
assertEquals("project1/test-1", m.getFromUri("/git/project1/test-1/ka/some/path").getName());
assertNull(m.getFromUri("/git/project1/test-3/ka/some/path"));
createUriTestRepositories(m);
assertEquals("scm-test", m.getFromUri("hg/namespace/scm-test").getName());
assertEquals("namespace", m.getFromUri("hg/namespace/scm-test").getNamespace());
}
@Test
public void getRepositoryFromRequestUri_withLeadingSlash() throws RepositoryException {
RepositoryManager m = createManager();
m.init(contextProvider);
createUriTestRepositories(m);
assertEquals("scm-test", m.getFromUri("/hg/namespace/scm-test").getName());
assertEquals("namespace", m.getFromUri("/hg/namespace/scm-test").getNamespace());
}
@Test
public void getRepositoryFromRequestUri_withPartialName() throws RepositoryException {
RepositoryManager m = createManager();
m.init(contextProvider);
createUriTestRepositories(m);
assertEquals("scm", m.getFromUri("hg/namespace/scm").getName());
assertEquals("namespace", m.getFromUri("hg/namespace/scm").getNamespace());
}
@Test
public void getRepositoryFromRequestUri_withTrailingFilePath() throws RepositoryException {
RepositoryManager m = createManager();
m.init(contextProvider);
createUriTestRepositories(m);
assertEquals("test-1", m.getFromUri("/git/namespace/test-1/ka/some/path").getName());
}
@Test
public void getRepositoryFromRequestUri_forNotExistingRepositoryName() throws RepositoryException {
RepositoryManager m = createManager();
m.init(contextProvider);
createUriTestRepositories(m);
assertNull(m.getFromUri("/git/namespace/test-3/ka/some/path"));
}
@Test
public void getRepositoryFromRequestUri_forWrongNamespace() throws RepositoryException {
RepositoryManager m = createManager();
m.init(contextProvider);
createUriTestRepositories(m);
assertNull(m.getFromUri("/git/other/other/test-2"));
}
@Test
public void shouldSetNamespace() throws RepositoryException {
Repository repository = new Repository(null, "hg", null, "scm");
manager.create(repository);
assertNotNull(repository.getId());
assertNotNull(repository.getNamespace());
}
private void createUriTestRepositories(RepositoryManager m) throws RepositoryException {
mockedNamespace = "namespace";
createRepository(m, new Repository("1", "hg", "namespace", "scm"));
createRepository(m, new Repository("2", "hg", "namespace", "scm-test"));
createRepository(m, new Repository("3", "git", "namespace", "test-1"));
createRepository(m, new Repository("4", "git", "namespace", "test-2"));
mockedNamespace = "other";
createRepository(m, new Repository("1", "hg", "other", "scm"));
createRepository(m, new Repository("2", "hg", "other", "scm-test"));
}
//~--- methods --------------------------------------------------------------
@@ -489,10 +480,11 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository, Re
this.configuration = new ScmConfiguration();
NamespaceStrategy namespaceStrategy = new DefaultNamespaceStrategy();
configuration.setEnableRepositoryArchive(archiveEnabled);
NamespaceStrategy namespaceStrategy = mock(NamespaceStrategy.class);
when(namespaceStrategy.getNamespace()).thenAnswer(invocation -> mockedNamespace);
return new DefaultRepositoryManager(configuration, contextProvider,
keyGenerator, repositoryDAO, handlerSet, createRepositoryMatcher(), namespaceStrategy);
}

View File

@@ -31,10 +31,13 @@
package sonia.scm.repository;
import com.google.common.collect.Sets;
import java.util.Set;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import java.util.Set;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link RepositoryMatcher}.
@@ -54,11 +57,11 @@ public class RepositoryMatcherTest {
@Test
public void testMatches() {
assertFalse(matcher.matches(repository("hg", "scm"), "hg", "scm-test/ka"));
assertFalse(matcher.matches(repository("git", "scm-test"), "hg", "scm-test"));
assertFalse(matcher.matches(repository("hg", "scm"), "hg", "namespace/scm-test/ka"));
assertFalse(matcher.matches(repository("git", "scm-test"), "hg", "namespace/scm-test"));
assertTrue(matcher.matches(repository("hg", "scm-test"), "hg", "scm-test/ka"));
assertTrue(matcher.matches(repository("hg", "scm-test"), "hg", "scm-test"));
assertTrue(matcher.matches(repository("hg", "scm-test"), "hg", "namespace/scm-test/ka"));
assertTrue(matcher.matches(repository("hg", "scm-test"), "hg", "namespace/scm-test"));
}
@Test
@@ -68,7 +71,7 @@ public class RepositoryMatcherTest {
}
private Repository repository(String type, String name) {
return new Repository(type + "-" + name, type, name);
return new Repository(type + "-" + name, type, "namespace", name);
}
private static class AbcRepositoryPathMatcher implements RepositoryPathMatcher {

View File

@@ -0,0 +1,37 @@
package sonia.scm.security;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import javax.servlet.http.HttpServletRequest;
import static org.junit.Assert.*;
import static org.mockito.Mockito.when;
/**
* Created by masuewer on 04.07.18.
*/
@RunWith(MockitoJUnitRunner.class)
public class SecurityRequestsTest {
@Mock
private HttpServletRequest request;
@Test
public void testIsAuthenticationRequestWithContextPath() {
when(request.getRequestURI()).thenReturn("/scm/api/rest/auth/access_token");
when(request.getContextPath()).thenReturn("/scm");
assertTrue(SecurityRequests.isAuthenticationRequest(request));
}
@Test
public void testIsAuthenticationRequest() throws Exception {
assertTrue(SecurityRequests.isAuthenticationRequest("/api/rest/auth/access_token"));
assertTrue(SecurityRequests.isAuthenticationRequest("/api/rest/v2/auth/access_token"));
assertFalse(SecurityRequests.isAuthenticationRequest("/api/rest/repositories"));
assertFalse(SecurityRequests.isAuthenticationRequest("/api/rest/v2/repositories"));
}
}

View File

@@ -0,0 +1,3 @@
{
"proxyPassword": "newPassword"
}

View File

@@ -0,0 +1,9 @@
[users]
readOnly = secret, reader
writeOnly = secret, writer
readWrite = secret, readerWriter
[roles]
reader = configuration:read
writer = configuration:write
readerWriter = configuration:*