merge with 2.0.0-m3

This commit is contained in:
Sebastian Sdorra
2018-10-17 15:54:25 +02:00
161 changed files with 5951 additions and 10483 deletions

View File

@@ -152,7 +152,7 @@ public class ScmServletModule extends ServletModule
public static final String PATTERN_PLUGIN_SCRIPT = "/plugins/resources/js/*";
/** Field description */
public static final String PATTERN_RESTAPI = "/api/rest/*";
public static final String PATTERN_RESTAPI = "/api/*";
/** Field description */
public static final String PATTERN_SCRIPT = "*.js";

View File

@@ -58,8 +58,10 @@ public class WebResourceServlet extends HttpServlet {
LOG.trace("try to load {}", uri);
URL url = webResourceLoader.getResource(uri);
if (url != null) {
LOG.trace("found {} -- serve as resource {}", uri, url);
serveResource(request, response, url);
} else {
LOG.trace("could not find {} -- dispatch", uri);
dispatch(request, response, uri);
}
}
@@ -79,6 +81,7 @@ public class WebResourceServlet extends HttpServlet {
private void serveResource(HttpServletRequest request, HttpServletResponse response, URL url) {
try {
LOG.debug("using sender to serve {}", request.getRequestURI());
sender.resource(url).send(request, response);
} catch (IOException ex) {
LOG.warn("failed to serve resource: {}", url);

View File

@@ -0,0 +1,14 @@
package sonia.scm.api.v2;
public final class ValidationConstraints {
private ValidationConstraints() {}
/**
* A user or group name should not start with <code>@</code> or a whitespace
* and it not contains whitespaces
* and the characters: . - _ @ are allowed
*/
public static final String USER_GROUP_PATTERN = "^[A-Za-z0-9\\.\\-_][A-Za-z0-9\\.\\-_@]*$";
}

View File

@@ -0,0 +1,79 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import org.hibernate.validator.constraints.NotEmpty;
import sonia.scm.ReducedModelObject;
import sonia.scm.group.GroupManager;
import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.validation.constraints.Size;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Path(AutoCompleteResource.PATH)
public class AutoCompleteResource {
public static final String PATH = "v2/autocomplete/";
public static final int MIN_SEARCHED_CHARS = 2;
public static final String PARAMETER_IS_REQUIRED = "The parameter is required.";
public static final String INVALID_PARAMETER_LENGTH = "Invalid parameter length.";
private ReducedObjectModelToDtoMapper mapper;
private UserManager userManager;
private GroupManager groupManager;
@Inject
public AutoCompleteResource(ReducedObjectModelToDtoMapper mapper, UserManager userManager, GroupManager groupManager) {
this.mapper = mapper;
this.userManager = userManager;
this.groupManager = groupManager;
}
@GET
@Path("users")
@Produces(VndMediaType.AUTOCOMPLETE)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 400, condition = "if the searched string contains less than 2 characters"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user:autocomplete\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
public List<ReducedObjectModelDto> searchUser(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) {
return map(userManager.autocomplete(filter));
}
@GET
@Path("groups")
@Produces(VndMediaType.AUTOCOMPLETE)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 400, condition = "if the searched string contains less than 2 characters"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group:autocomplete\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
public List<ReducedObjectModelDto> searchGroup(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) {
return map(groupManager.autocomplete(filter));
}
private <T extends ReducedModelObject> List<ReducedObjectModelDto> map(Collection<T> autocomplete) {
return autocomplete
.stream()
.map(mapper::map)
.collect(Collectors.toList());
}
}

View File

@@ -12,12 +12,12 @@ public class BranchChangesetCollectionToDtoMapper extends ChangesetCollectionToD
@Inject
public BranchChangesetCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) {
super(changesetToChangesetDtoMapper);
super(changesetToChangesetDtoMapper, resourceLinks);
this.resourceLinks = resourceLinks;
}
public CollectionDto map(int pageNumber, int pageSize, PageResult<Changeset> pageResult, Repository repository, String branch) {
return this.map(pageNumber, pageSize, pageResult, repository, () -> createSelfLink(repository, branch));
return this.map(pageNumber, pageSize, pageResult, repository, () -> createSelfLink(repository, branch), branch);
}
private String createSelfLink(Repository repository, String branch) {

View File

@@ -0,0 +1,19 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@NoArgsConstructor @AllArgsConstructor @Getter @Setter
public class BranchReferenceDto extends HalRepresentation {
private String name;
@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

@@ -12,10 +12,13 @@ public class ChangesetCollectionToDtoMapper extends ChangesetCollectionToDtoMapp
@Inject
public ChangesetCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) {
super(changesetToChangesetDtoMapper);
super(changesetToChangesetDtoMapper, resourceLinks);
this.resourceLinks = resourceLinks;
}
public CollectionDto map(int pageNumber, int pageSize, PageResult<Changeset> pageResult, Repository repository, String branchName) {
return super.map(pageNumber, pageSize, pageResult, repository, () -> createSelfLink(repository), branchName);
}
public CollectionDto map(int pageNumber, int pageSize, PageResult<Changeset> pageResult, Repository repository) {
return super.map(pageNumber, pageSize, pageResult, repository, () -> createSelfLink(repository));
}

View File

@@ -1,5 +1,6 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import sonia.scm.PageResult;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.Repository;
@@ -10,14 +11,28 @@ import java.util.function.Supplier;
class ChangesetCollectionToDtoMapperBase extends PagedCollectionToDtoMapper<Changeset, ChangesetDto> {
private final ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper;
private final ResourceLinks resourceLinks;
ChangesetCollectionToDtoMapperBase(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper) {
ChangesetCollectionToDtoMapperBase(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) {
super("changesets");
this.changesetToChangesetDtoMapper = changesetToChangesetDtoMapper;
this.resourceLinks = resourceLinks;
}
CollectionDto map(int pageNumber, int pageSize, PageResult<Changeset> pageResult, Repository repository, Supplier<String> selfLinkSupplier) {
return super.map(pageNumber, pageSize, pageResult, selfLinkSupplier.get(), Optional.empty(), changeset -> changesetToChangesetDtoMapper.map(changeset, repository));
}
}
CollectionDto map(int pageNumber, int pageSize, PageResult<Changeset> pageResult, Repository repository, Supplier<String> selfLinkSupplier, String branchName) {
CollectionDto collectionDto = this.map(pageNumber, pageSize, pageResult, repository, selfLinkSupplier);
collectionDto.withEmbedded("branch", createBranchReferenceDto(repository, branchName));
return collectionDto;
}
private BranchReferenceDto createBranchReferenceDto(Repository repository, String branchName) {
BranchReferenceDto branchReferenceDto = new BranchReferenceDto();
branchReferenceDto.setName(branchName);
branchReferenceDto.add(Links.linkingTo().self(resourceLinks.branch().self(repository.getNamespaceAndName(), branchName)).build());
return branchReferenceDto;
}
}

View File

@@ -67,7 +67,11 @@ public class ChangesetRootResource {
.getChangesets();
if (changesets != null && changesets.getChangesets() != null) {
PageResult<Changeset> pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal());
return Response.ok(changesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository)).build();
if (changesets.getBranchName() != null) {
return Response.ok(changesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository, changesets.getBranchName())).build();
} else {
return Response.ok(changesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository)).build();
}
} else {
return Response.ok().build();
}

View File

@@ -15,4 +15,9 @@ class CollectionDto extends HalRepresentation {
CollectionDto(Links links, Embedded embedded) {
super(links, embedded);
}
@Override
protected HalRepresentation withEmbedded(String rel, HalRepresentation embeddedItem) {
return super.withEmbedded(rel, embeddedItem);
}
}

View File

@@ -13,7 +13,7 @@ public class FileHistoryCollectionToDtoMapper extends ChangesetCollectionToDtoMa
@Inject
public FileHistoryCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) {
super(changesetToChangesetDtoMapper);
super(changesetToChangesetDtoMapper, resourceLinks);
this.resourceLinks = resourceLinks;
}

View File

@@ -13,6 +13,8 @@ import java.time.Instant;
import java.util.List;
import java.util.Map;
import static sonia.scm.api.v2.ValidationConstraints.USER_GROUP_PATTERN;
@Getter @Setter @NoArgsConstructor
public class GroupDto extends HalRepresentation {
@@ -20,7 +22,7 @@ public class GroupDto extends HalRepresentation {
private String description;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Instant lastModified;
@Pattern(regexp = "^[A-z0-9\\.\\-_@]|[^ ]([A-z0-9\\.\\-_@ ]*[A-z0-9\\.\\-_@]|[^ ])?$")
@Pattern(regexp = USER_GROUP_PATTERN)
private String name;
private String type;
private Map<String, String> properties;

View File

@@ -0,0 +1,16 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
@Getter
public class IndexDto extends HalRepresentation {
private final String version;
IndexDto(String version, Links links) {
super(links);
this.version = version;
}
}

View File

@@ -0,0 +1,61 @@
package sonia.scm.api.v2.resources;
import com.google.common.collect.Lists;
import de.otto.edison.hal.Link;
import de.otto.edison.hal.Links;
import org.apache.shiro.SecurityUtils;
import sonia.scm.SCMContextProvider;
import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.group.GroupPermissions;
import sonia.scm.user.UserPermissions;
import javax.inject.Inject;
import java.util.List;
import static de.otto.edison.hal.Link.link;
public class IndexDtoGenerator {
private final ResourceLinks resourceLinks;
private final SCMContextProvider scmContextProvider;
@Inject
public IndexDtoGenerator(ResourceLinks resourceLinks, SCMContextProvider scmContextProvider) {
this.resourceLinks = resourceLinks;
this.scmContextProvider = scmContextProvider;
}
public IndexDto generate() {
Links.Builder builder = Links.linkingTo();
List<Link> autoCompleteLinks = Lists.newArrayList();
builder.self(resourceLinks.index().self());
builder.single(link("uiPlugins", resourceLinks.uiPluginCollection().self()));
if (SecurityUtils.getSubject().isAuthenticated()) {
builder.single(
link("me", resourceLinks.me().self()),
link("logout", resourceLinks.authentication().logout())
);
if (UserPermissions.list().isPermitted()) {
builder.single(link("users", resourceLinks.userCollection().self()));
}
if (UserPermissions.autocomplete().isPermitted()) {
autoCompleteLinks.add(Link.linkBuilder("autocomplete", resourceLinks.autoComplete().users()).withName("users").build());
}
if (GroupPermissions.autocomplete().isPermitted()) {
autoCompleteLinks.add(Link.linkBuilder("autocomplete", resourceLinks.autoComplete().groups()).withName("groups").build());
}
builder.array(autoCompleteLinks);
if (GroupPermissions.list().isPermitted()) {
builder.single(link("groups", resourceLinks.groupCollection().self()));
}
if (ConfigurationPermissions.list().isPermitted()) {
builder.single(link("config", resourceLinks.config().self()));
}
builder.single(link("repositories", resourceLinks.repositoryCollection().self()));
} else {
builder.single(link("login", resourceLinks.authentication().jsonLogin()));
}
return new IndexDto(scmContextProvider.getVersion(), builder.build());
}
}

View File

@@ -0,0 +1,29 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@Path(IndexResource.INDEX_PATH_V2)
public class IndexResource {
public static final String INDEX_PATH_V2 = "v2/";
private final IndexDtoGenerator indexDtoGenerator;
@Inject
public IndexResource(IndexDtoGenerator indexDtoGenerator) {
this.indexDtoGenerator = indexDtoGenerator;
}
@GET
@Path("")
@Produces(VndMediaType.INDEX)
@TypeHint(IndexDto.class)
public IndexDto getIndex() {
return indexDtoGenerator.generate();
}
}

View File

@@ -37,6 +37,8 @@ public class MapperModule extends AbstractModule {
bind(FileObjectToFileObjectDtoMapper.class).to(Mappers.getMapper(FileObjectToFileObjectDtoMapper.class).getClass());
bind(ModificationsToDtoMapper.class).to(Mappers.getMapper(ModificationsToDtoMapper.class).getClass());
bind(ReducedObjectModelToDtoMapper.class).to(Mappers.getMapper(ReducedObjectModelToDtoMapper.class).getClass());
// no mapstruct required
bind(UIPluginDtoMapper.class);
bind(UIPluginDtoCollectionMapper.class);

View File

@@ -4,15 +4,20 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter @Setter @ToString
import javax.validation.constraints.Pattern;
import static sonia.scm.api.v2.ValidationConstraints.USER_GROUP_PATTERN;
@Getter @Setter @ToString @NoArgsConstructor
public class PermissionDto extends HalRepresentation {
public static final String GROUP_PREFIX = "@";
@JsonInclude(JsonInclude.Include.NON_NULL)
@Pattern(regexp = USER_GROUP_PATTERN)
private String name;
/**
@@ -28,9 +33,6 @@ public class PermissionDto extends HalRepresentation {
private boolean groupPermission = false;
public PermissionDto() {
}
public PermissionDto(String permissionName, boolean groupPermission) {
name = permissionName;
this.groupPermission = groupPermission;

View File

@@ -16,6 +16,7 @@ import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
@@ -70,14 +71,15 @@ public class PermissionRootResource {
@TypeHint(TypeHint.NO_CONTENT.class)
@Consumes(VndMediaType.PERMISSION)
@Path("")
public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name, PermissionDto permission) throws Exception {
public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name,@Valid PermissionDto permission) throws AlreadyExistsException, NotFoundException {
log.info("try to add new permission: {}", permission);
Repository repository = load(namespace, name);
RepositoryPermissions.permissionWrite(repository).check();
checkPermissionAlreadyExists(permission, repository);
repository.getPermissions().add(dtoToModelMapper.map(permission));
manager.modify(repository);
return Response.created(URI.create(resourceLinks.permission().self(namespace, name, permission.getName()))).build();
String urlPermissionName = modelToDtoMapper.getUrlPermissionName(permission);
return Response.created(URI.create(resourceLinks.permission().self(namespace, name, urlPermissionName))).build();
}
@@ -156,7 +158,7 @@ public class PermissionRootResource {
public Response update(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@PathParam("permission-name") String permissionName,
PermissionDto permission) throws NotFoundException, AlreadyExistsException {
@Valid PermissionDto permission) throws NotFoundException, AlreadyExistsException {
log.info("try to update the permission with name: {}. the modified permission is: {}", permissionName, permission);
Repository repository = load(namespace, name);
RepositoryPermissions.permissionWrite(repository).check();

View File

@@ -41,9 +41,7 @@ public abstract class PermissionToPermissionDtoMapper {
*/
@AfterMapping
void appendLinks(@MappingTarget PermissionDto target, @Context Repository repository) {
String permissionName = Optional.of(target.getName())
.filter(p -> !target.isGroupPermission())
.orElse(GROUP_PREFIX + target.getName());
String permissionName = getUrlPermissionName(target);
Links.Builder linksBuilder = linkingTo()
.self(resourceLinks.permission().self(repository.getNamespace(), repository.getName(), permissionName));
if (RepositoryPermissions.permissionWrite(repository).isPermitted()) {
@@ -52,4 +50,10 @@ public abstract class PermissionToPermissionDtoMapper {
}
target.add(linksBuilder.build());
}
public String getUrlPermissionName(PermissionDto permissionDto) {
return Optional.of(permissionDto.getName())
.filter(p -> !permissionDto.isGroupPermission())
.orElse(GROUP_PREFIX + permissionDto.getName());
}
}

View File

@@ -0,0 +1,16 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class ReducedObjectModelDto extends HalRepresentation {
private String id;
private String displayName;
}

View File

@@ -0,0 +1,13 @@
package sonia.scm.api.v2.resources;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import sonia.scm.ReducedModelObject;
@Mapper
public abstract class ReducedObjectModelToDtoMapper {
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
public abstract ReducedObjectModelDto map(ReducedModelObject modelObject);
}

View File

@@ -3,7 +3,6 @@ package sonia.scm.api.v2.resources;
import sonia.scm.repository.NamespaceAndName;
import javax.inject.Inject;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
class ResourceLinks {
@@ -143,6 +142,26 @@ class ResourceLinks {
}
}
AutoCompleteLinks autoComplete() {
return new AutoCompleteLinks (scmPathInfoStore.get());
}
static class AutoCompleteLinks {
private final LinkBuilder linkBuilder;
AutoCompleteLinks (ScmPathInfo pathInfo) {
linkBuilder = new LinkBuilder(pathInfo, AutoCompleteResource.class);
}
String users() {
return linkBuilder.method("searchUser").parameters().href();
}
String groups() {
return linkBuilder.method("searchGroup").parameters().href();
}
}
ConfigLinks config() {
return new ConfigLinks(scmPathInfoStore.get());
}
@@ -441,7 +460,6 @@ class ResourceLinks {
}
}
public UIPluginLinks uiPlugin() {
return new UIPluginLinks(scmPathInfoStore.get());
}
@@ -473,4 +491,45 @@ class ResourceLinks {
return uiPluginCollectionLinkBuilder.method("plugins").parameters().method("getInstalledPlugins").parameters().href();
}
}
public AuthenticationLinks authentication() {
return new AuthenticationLinks(scmPathInfoStore.get());
}
static class AuthenticationLinks {
private final LinkBuilder loginLinkBuilder;
AuthenticationLinks(ScmPathInfo pathInfo) {
this.loginLinkBuilder = new LinkBuilder(pathInfo, AuthenticationResource.class);
}
String formLogin() {
return loginLinkBuilder.method("authenticateViaForm").parameters().href();
}
String jsonLogin() {
return loginLinkBuilder.method("authenticateViaJSONBody").parameters().href();
}
String logout() {
return loginLinkBuilder.method("logout").parameters().href();
}
}
public IndexLinks index() {
return new IndexLinks(scmPathInfoStore.get());
}
static class IndexLinks {
private final LinkBuilder indexLinkBuilder;
IndexLinks(ScmPathInfo pathInfo) {
indexLinkBuilder = new LinkBuilder(pathInfo, IndexResource.class);
}
String self() {
return indexLinkBuilder.method("getIndex").parameters().href();
}
}
}

View File

@@ -13,6 +13,8 @@ import javax.validation.constraints.Pattern;
import java.time.Instant;
import java.util.Map;
import static sonia.scm.api.v2.ValidationConstraints.USER_GROUP_PATTERN;
@NoArgsConstructor @Getter @Setter
public class UserDto extends HalRepresentation {
private boolean active;
@@ -24,7 +26,7 @@ public class UserDto extends HalRepresentation {
private Instant lastModified;
@NotEmpty @Email
private String mail;
@Pattern(regexp = "^[A-z0-9\\.\\-_@]|[^ ]([A-z0-9\\.\\-_@ ]*[A-z0-9\\.\\-_@]|[^ ])?$")
@Pattern(regexp = USER_GROUP_PATTERN)
private String name;
@JsonInclude(JsonInclude.Include.NON_NULL)
private String password;

View File

@@ -84,7 +84,7 @@ public class SecurityFilter extends HttpFilter
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException
{
if (!SecurityRequests.isAuthenticationRequest(request))
if (!SecurityRequests.isAuthenticationRequest(request) && !SecurityRequests.isIndexRequest(request))
{
Subject subject = SecurityUtils.getSubject();
if (hasPermission(subject))

View File

@@ -242,6 +242,13 @@ public class DefaultGroupManager extends AbstractGroupManager
return group;
}
@Override
public Collection<Group> autocomplete(String filter) {
GroupPermissions.autocomplete().check();
SearchRequest searchRequest = new SearchRequest(filter, true, DEFAULT_LIMIT);
return SearchUtil.search(searchRequest, groupDAO.getAll(), group -> matches(searchRequest,group)?group:null);
}
/**
* Method description
*

View File

@@ -52,10 +52,13 @@ import org.slf4j.LoggerFactory;
import sonia.scm.cache.Cache;
import sonia.scm.cache.CacheManager;
import sonia.scm.group.GroupNames;
import sonia.scm.group.GroupPermissions;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryDAO;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.user.User;
import sonia.scm.user.UserPermissions;
import sonia.scm.util.Util;
import java.util.List;
@@ -74,7 +77,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
// TODO move to util class
private static final String SEPARATOR = System.getProperty("line.separator", "\n");
/** Field description */
private static final String ADMIN_PERMISSION = "*";
@@ -88,7 +91,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
LoggerFactory.getLogger(DefaultAuthorizationCollector.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
@@ -209,7 +212,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
String perm = permission.getType().getPermissionPrefix().concat(repository.getId());
if (logger.isTraceEnabled())
{
logger.trace("add repository permission {} for user {} at repository {}",
logger.trace("add repository permission {} for user {} at repository {}",
perm, user.getName(), repository.getName());
}
@@ -254,6 +257,9 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
collectGlobalPermissions(builder, user, groups);
collectRepositoryPermissions(builder, user, groups);
builder.add(canReadOwnUser(user));
builder.add(getUserAutocompletePermission());
builder.add(getGroupAutocompletePermission());
permissions = builder.build();
}
@@ -262,6 +268,18 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
return info;
}
private String getGroupAutocompletePermission() {
return GroupPermissions.autocomplete().asShiroString();
}
private String getUserAutocompletePermission() {
return UserPermissions.autocomplete().asShiroString();
}
private String canReadOwnUser(User user) {
return UserPermissions.read(user.getName()).asShiroString();
}
//~--- get methods ----------------------------------------------------------
private boolean isUserPermitted(User user, GroupNames groups,
@@ -272,7 +290,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|| ((!perm.isGroupPermission()) && user.getName().equals(perm.getName()));
//J+
}
@Subscribe
public void invalidateCache(AuthorizationChangedEvent event) {
if (event.isEveryUserAffected()) {
@@ -281,12 +299,12 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
invalidateCache();
}
}
private void invalidateUserCache(final String username) {
logger.info("invalidate cache for user {}, because of a received authorization event", username);
cache.removeAll((CacheKey item) -> username.equalsIgnoreCase(item.username));
}
private void invalidateCache() {
logger.info("invalidate cache, because of a received authorization event");
cache.clear();

View File

@@ -11,6 +11,7 @@ import static sonia.scm.api.v2.resources.ScmPathInfo.REST_API_PATH;
public final class SecurityRequests {
private static final Pattern URI_LOGIN_PATTERN = Pattern.compile(REST_API_PATH + "(?:/v2)?/auth/access_token");
private static final Pattern URI_INDEX_PATTERN = Pattern.compile(REST_API_PATH + "/v2/?");
private SecurityRequests() {}
@@ -23,4 +24,13 @@ public final class SecurityRequests {
return URI_LOGIN_PATTERN.matcher(uri).matches();
}
public static boolean isIndexRequest(HttpServletRequest request) {
String uri = request.getRequestURI().substring(request.getContextPath().length());
return isIndexRequest(uri);
}
public static boolean isIndexRequest(String uri) {
return URI_INDEX_PATTERN.matcher(uri).matches();
}
}

View File

@@ -229,6 +229,13 @@ public class DefaultUserManager extends AbstractUserManager
fresh.copyProperties(user);
}
@Override
public Collection<User> autocomplete(String filter) {
UserPermissions.autocomplete().check();
SearchRequest searchRequest = new SearchRequest(filter, true, DEFAULT_LIMIT);
return SearchUtil.search(searchRequest, userDAO.getAll(), user -> matches(searchRequest,user)?user:null);
}
/**
* Method description
*
@@ -258,7 +265,7 @@ public class DefaultUserManager extends AbstractUserManager
}
});
}
private boolean matches(SearchRequest searchRequest, User user) {
return SearchUtil.matchesOne(searchRequest, user.getName(), user.getDisplayName(), user.getMail());
}
@@ -277,7 +284,7 @@ public class DefaultUserManager extends AbstractUserManager
public User get(String id)
{
UserPermissions.read().check(id);
User user = userDAO.get(id);
if (user != null)

View File

@@ -99,7 +99,7 @@ public class ApiAuthenticationFilter extends AuthenticationFilter
throws IOException, ServletException
{
// skip filter on login resource
if (SecurityRequests.isAuthenticationRequest(request))
if (SecurityRequests.isAuthenticationRequest(request) )
{
chain.doFilter(request, response);
}