mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 15:35:49 +01:00
merge
This commit is contained in:
@@ -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\\.\\-_@]*$";
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@NotEmpty
|
||||
private String type;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
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 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();
|
||||
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 (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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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,7 +71,7 @@ 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();
|
||||
@@ -156,7 +157,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();
|
||||
|
||||
@@ -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 {
|
||||
@@ -441,7 +440,6 @@ class ResourceLinks {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public UIPluginLinks uiPlugin() {
|
||||
return new UIPluginLinks(scmPathInfoStore.get());
|
||||
}
|
||||
@@ -473,4 +471,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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -56,6 +56,7 @@ import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryDAO;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserPermissions;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
import java.util.List;
|
||||
@@ -74,7 +75,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 +89,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
LoggerFactory.getLogger(DefaultAuthorizationCollector.class);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
@@ -209,7 +210,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 +255,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
|
||||
collectGlobalPermissions(builder, user, groups);
|
||||
collectRepositoryPermissions(builder, user, groups);
|
||||
builder.add(canReadOwnUser(user));
|
||||
permissions = builder.build();
|
||||
}
|
||||
|
||||
@@ -262,6 +264,10 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
return info;
|
||||
}
|
||||
|
||||
private String canReadOwnUser(User user) {
|
||||
return UserPermissions.read(user.getName()).asShiroString();
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
private boolean isUserPermitted(User user, GroupNames groups,
|
||||
@@ -272,7 +278,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 +287,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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user