Implement global config endpoint v2

This commit is contained in:
Michael Behlendorf
2018-07-06 14:45:00 +02:00
parent 50b6b58692
commit 8c68a2de24
12 changed files with 493 additions and 1 deletions

View File

@@ -0,0 +1,58 @@
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 sonia.scm.xml.XmlSetStringAdapter;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.util.Set;
@NoArgsConstructor
@Getter
@Setter
public class GlobalConfigDto 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;
@XmlElement(name = "admin-groups")
@XmlJavaTypeAdapter(XmlSetStringAdapter.class)
private Set<String> adminGroups;
@XmlElement(name = "admin-users")
@XmlJavaTypeAdapter(XmlSetStringAdapter.class)
private Set<String> adminUsers;
@XmlElement(name = "base-url")
private String baseUrl;
@XmlElement(name = "force-base-url")
private boolean forceBaseUrl;
@XmlElement(name = "login-attempt-limit")
private int loginAttemptLimit;
@XmlElement(name = "proxy-excludes")
@XmlJavaTypeAdapter(XmlSetStringAdapter.class)
private Set<String> proxyExcludes;
@XmlElement(name = "skip-failed-authenticators")
private boolean skipFailedAuthenticators;
@XmlElement(name = "plugin-url")
private String pluginUrl;
@XmlElement(name = "login-attempt-limit-timeout")
private long loginAttemptLimitTimeout;
@XmlElement(name = "xsrf-protection")
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 GlobalConfigDtoToScmConfigurationMapper {
public abstract ScmConfiguration map(GlobalConfigDto dto);
}

View File

@@ -0,0 +1,91 @@
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.config.ScmConfiguration;
import sonia.scm.security.Role;
import sonia.scm.util.ScmConfigurationUtil;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
@Path(GlobalConfigResource.GLOBAL_CONFIG_PATH_V2)
public class GlobalConfigResource {
static final String GLOBAL_CONFIG_PATH_V2 = "v2/config/global";
private final GlobalConfigDtoToScmConfigurationMapper dtoToConfigMapper;
private final ScmConfigurationToGlobalConfigDtoMapper configToDtoMapper;
private final ScmConfiguration configuration;
@Inject
public GlobalConfigResource(GlobalConfigDtoToScmConfigurationMapper dtoToConfigMapper, ScmConfigurationToGlobalConfigDtoMapper configToDtoMapper, ScmConfiguration configuration) {
this.dtoToConfigMapper = dtoToConfigMapper;
this.configToDtoMapper = configToDtoMapper;
this.configuration = configuration;
}
/**
* Returns the global scm config.
*/
@GET
@Path("")
@Produces(VndMediaType.GLOBAL_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() {
Response response;
// TODO ConfigPermisions?
if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) {
response = Response.ok(configToDtoMapper.map(configuration)).build();
} else {
response = Response.status(Response.Status.FORBIDDEN).build();
}
return response;
}
/**
* Modifies the global scm config.
*
* @param configDto new global scm configuration as DTO
*/
@PUT
@Path("")
@Consumes(VndMediaType.GLOBAL_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(GlobalConfigDto configDto, @Context UriInfo uriInfo) {
Response response;
// TODO ConfigPermisions?
if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) {
ScmConfiguration config = dtoToConfigMapper.map(configDto);
configuration.load(config);
synchronized (ScmConfiguration.class) {
ScmConfigurationUtil.getInstance().store(configuration);
}
response = Response.created(uriInfo.getRequestUri()).build();
} else {
response = Response.status(Response.Status.FORBIDDEN).build();
}
return response;
}
}

View File

@@ -15,6 +15,9 @@ public class MapperModule extends AbstractModule {
bind(GroupToGroupDtoMapper.class).to(Mappers.getMapper(GroupToGroupDtoMapper.class).getClass());
bind(GroupCollectionToDtoMapper.class);
bind(ScmConfigurationToGlobalConfigDtoMapper.class).to(Mappers.getMapper(ScmConfigurationToGlobalConfigDtoMapper.class).getClass());
bind(GlobalConfigDtoToScmConfigurationMapper.class).to(Mappers.getMapper(GlobalConfigDtoToScmConfigurationMapper.class).getClass());
bind(UriInfoStore.class).in(ServletScopes.REQUEST);
}
}

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();
}
}
@@ -100,4 +100,24 @@ class ResourceLinks {
return collectionLinkBuilder.method("getUserCollectionResource").parameters().method("create").parameters().href();
}
}
GlobalConfigLinks globalConfig() {
return new GlobalConfigLinks(uriInfoStore.get());
}
static class GlobalConfigLinks {
private final LinkBuilder globalConfigLinkBuilder;
private GlobalConfigLinks(UriInfo uriInfo) {
globalConfigLinkBuilder = new LinkBuilder(uriInfo, GlobalConfigResource.class);
}
String self() {
return globalConfigLinkBuilder.method("get").parameters().href();
}
String update() {
return globalConfigLinkBuilder.method("update").parameters().href();
}
}
}

View File

@@ -0,0 +1,36 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import org.apache.shiro.SecurityUtils;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.security.Role;
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 ScmConfigurationToGlobalConfigDtoMapper {
@Inject
private ResourceLinks resourceLinks;
public abstract GlobalConfigDto map(ScmConfiguration config);
@AfterMapping
void appendLinks(ScmConfiguration config, @MappingTarget GlobalConfigDto target) {
Links.Builder linksBuilder = linkingTo().self(resourceLinks.globalConfig().self());
// TODO: ConfigPermissions?
if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) {
linksBuilder.single(link("update", resourceLinks.globalConfig().update()));
}
target.add(linksBuilder.build());
}
}