From 357ccc7ddb1e3a918da4fb8d36c188bcf8ab78ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 26 Sep 2018 17:00:13 +0200 Subject: [PATCH] Introduce index resource with first links --- .../sonia/scm/api/v2/resources/IndexDto.java | 11 +++++ .../api/v2/resources/IndexDtoGenerator.java | 34 ++++++++++++++ .../scm/api/v2/resources/IndexResource.java | 22 ++++++++++ .../scm/api/v2/resources/ResourceLinks.java | 25 ++++++++++- .../DefaultAuthorizationCollector.java | 18 +++++--- .../api/v2/resources/IndexResourceTest.java | 44 +++++++++++++++++++ .../api/v2/resources/ResourceLinksMock.java | 1 + 7 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDto.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexResource.java create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexResourceTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDto.java new file mode 100644 index 0000000000..699ba1ea83 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDto.java @@ -0,0 +1,11 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; + +public class IndexDto extends HalRepresentation { + + IndexDto(Links links) { + super(links); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java new file mode 100644 index 0000000000..1e969824bf --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java @@ -0,0 +1,34 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Link; +import de.otto.edison.hal.Links; +import org.apache.shiro.SecurityUtils; + +import javax.inject.Inject; + +public class IndexDtoGenerator { + + private final ResourceLinks resourceLinks; + + @Inject + public IndexDtoGenerator(ResourceLinks resourceLinks) { + this.resourceLinks = resourceLinks; + } + + public IndexDto generate() { + Links.Builder builder = Links.linkingTo(); + if (SecurityUtils.getSubject().isAuthenticated()) { + builder.single( + Link.link("me", resourceLinks.me().self()), + Link.link("logout", resourceLinks.authentication().logout()) + ); + } else { + builder.single( + Link.link("formLogin", resourceLinks.authentication().formLogin()), + Link.link("jsonLogin", resourceLinks.authentication().jsonLogin()) + ); + } + + return new IndexDto(builder.build()); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexResource.java new file mode 100644 index 0000000000..a6e977d661 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexResource.java @@ -0,0 +1,22 @@ +package sonia.scm.api.v2.resources; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@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 + public IndexDto getIndex() { + return indexDtoGenerator.generate(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index e978de443a..02d7a3d53d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -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 { @@ -473,4 +472,28 @@ 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(); + } + } } diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java index 1b9abb058b..cd22e22712 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java @@ -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 "user:" + UserPermissions.ACTION_READ + ":" + user.getName(); + } + //~--- 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(); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexResourceTest.java new file mode 100644 index 0000000000..f3f6c94006 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexResourceTest.java @@ -0,0 +1,44 @@ +package sonia.scm.api.v2.resources; + +import com.github.sdorra.shiro.ShiroRule; +import com.github.sdorra.shiro.SubjectAware; +import org.assertj.core.api.Assertions; +import org.junit.Rule; +import org.junit.Test; + +import java.net.URI; +import java.util.Optional; + +@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini") +public class IndexResourceTest { + + @Rule + public final ShiroRule shiroRule = new ShiroRule(); + + private final IndexDtoGenerator indexDtoGenerator = new IndexDtoGenerator(ResourceLinksMock.createMock(URI.create("/"))); + private final IndexResource indexResource = new IndexResource(indexDtoGenerator); + + @Test + public void shouldRenderLoginUrlsForUnauthenticatedRequest() { + IndexDto index = indexResource.getIndex(); + + Assertions.assertThat(index.getLinks().getLinkBy("formLogin")).matches(Optional::isPresent); + Assertions.assertThat(index.getLinks().getLinkBy("jsonLogin")).matches(Optional::isPresent); + } + + @Test + @SubjectAware(username = "trillian", password = "secret") + public void shouldRenderMeUrlForAuthenticatedRequest() { + IndexDto index = indexResource.getIndex(); + + Assertions.assertThat(index.getLinks().getLinkBy("me")).matches(Optional::isPresent); + } + + @Test + @SubjectAware(username = "trillian", password = "secret") + public void shouldRenderLogoutUrlForAuthenticatedRequest() { + IndexDto index = indexResource.getIndex(); + + Assertions.assertThat(index.getLinks().getLinkBy("logout")).matches(Optional::isPresent); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java index c70510fe39..2e2c1ae586 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java @@ -34,6 +34,7 @@ public class ResourceLinksMock { when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(uriInfo)); when(resourceLinks.uiPluginCollection()).thenReturn(new ResourceLinks.UIPluginCollectionLinks(uriInfo)); when(resourceLinks.uiPlugin()).thenReturn(new ResourceLinks.UIPluginLinks(uriInfo)); + when(resourceLinks.authentication()).thenReturn(new ResourceLinks.AuthenticationLinks(uriInfo)); return resourceLinks; }