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

@@ -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);