diff --git a/Jenkinsfile b/Jenkinsfile index e817ac2ba1..0f586bf346 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -51,9 +51,9 @@ node('docker') { if (isMainBranch()) { - stage('Lifecycle') { - nexusPolicyEvaluation iqApplication: selectedApplication('scm'), iqScanPatterns: [[scanPattern: 'scm-server/target/scm-server-app.zip']], iqStage: 'build' - } +// stage('Lifecycle') { +// nexusPolicyEvaluation iqApplication: selectedApplication('scm'), iqScanPatterns: [[scanPattern: 'scm-server/target/scm-server-app.zip']], iqStage: 'build' +// } stage('Archive') { archiveArtifacts 'scm-webapp/target/scm-webapp.war' diff --git a/pom.xml b/pom.xml index a7108df873..d8437f2934 100644 --- a/pom.xml +++ b/pom.xml @@ -826,7 +826,7 @@ 9.4.14.v20181114 - 1.1.0 + 1.2.0 1.4.0 diff --git a/scm-core/src/main/java/sonia/scm/config/Configuration.java b/scm-core/src/main/java/sonia/scm/config/Configuration.java index 823c50b155..4019925c27 100644 --- a/scm-core/src/main/java/sonia/scm/config/Configuration.java +++ b/scm-core/src/main/java/sonia/scm/config/Configuration.java @@ -22,7 +22,8 @@ import com.github.sdorra.ssp.StaticPermissions; @StaticPermissions( value = "configuration", permissions = {"read", "write"}, - globalPermissions = {"list"} + globalPermissions = {"list"}, + custom = true, customGlobal = true ) public interface Configuration extends PermissionObject { } diff --git a/scm-core/src/main/java/sonia/scm/group/Group.java b/scm-core/src/main/java/sonia/scm/group/Group.java index c0b3c2ee8b..8860545c93 100644 --- a/scm-core/src/main/java/sonia/scm/group/Group.java +++ b/scm-core/src/main/java/sonia/scm/group/Group.java @@ -61,7 +61,11 @@ import java.util.List; * * @author Sebastian Sdorra */ -@StaticPermissions(value = "group", globalPermissions = {"create", "list", "autocomplete"}) +@StaticPermissions( + value = "group", + globalPermissions = {"create", "list", "autocomplete"}, + custom = true, customGlobal = true +) @XmlRootElement(name = "groups") @XmlAccessorType(XmlAccessType.FIELD) public class Group extends BasicPropertiesAware diff --git a/scm-core/src/main/java/sonia/scm/group/GroupNames.java b/scm-core/src/main/java/sonia/scm/group/GroupNames.java index 24d32972b6..faeec7218b 100644 --- a/scm-core/src/main/java/sonia/scm/group/GroupNames.java +++ b/scm-core/src/main/java/sonia/scm/group/GroupNames.java @@ -70,21 +70,9 @@ public final class GroupNames implements Serializable, Iterable * Constructs ... * */ - @SuppressWarnings("unchecked") public GroupNames() { - this.collection = Collections.EMPTY_LIST; - } - - /** - * Constructs ... - * - * - * @param collection - */ - public GroupNames(Collection collection) - { - this.collection = Collections.unmodifiableCollection(collection); + this(Collections.emptyList()); } /** @@ -96,7 +84,30 @@ public final class GroupNames implements Serializable, Iterable */ public GroupNames(String groupName, String... groupNames) { - this.collection = Lists.asList(groupName, groupNames); + this(Lists.asList(groupName, groupNames)); + } + + /** + * Constructs ... + * + * + * @param collection + */ + public GroupNames(Collection collection) + { + this(collection, false); + } + + /** + * Constructs ... + * + * + * @param collection + */ + public GroupNames(Collection collection, boolean external) + { + this.collection = Collections.unmodifiableCollection(collection); + this.external = external; } //~--- methods -------------------------------------------------------------- @@ -165,7 +176,7 @@ public final class GroupNames implements Serializable, Iterable @Override public String toString() { - return Joiner.on(", ").join(collection); + return Joiner.on(", ").join(collection) + "(" + (external? "external": "internal") + ")"; } //~--- get methods ---------------------------------------------------------- @@ -181,8 +192,13 @@ public final class GroupNames implements Serializable, Iterable return collection; } - //~--- fields --------------------------------------------------------------- + public boolean isExternal() { + return external; + } + //~--- fields --------------------------------------------------------------- /** Field description */ private final Collection collection; + + private final boolean external; } diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java b/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java index 3ae359ceb7..6de52c3cca 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java @@ -61,7 +61,8 @@ import java.util.List; value = "plugin", generatedClass = "PluginPermissions", permissions = {}, - globalPermissions = { "read", "manage" } + globalPermissions = { "read", "manage" }, + custom = true, customGlobal = true ) @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "plugin-information") diff --git a/scm-core/src/main/java/sonia/scm/repository/Repository.java b/scm-core/src/main/java/sonia/scm/repository/Repository.java index 568b75e525..18613c1a12 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -62,7 +62,8 @@ import java.util.Set; */ @StaticPermissions( value = "repository", - permissions = {"read", "modify", "delete", "healthCheck", "pull", "push", "permissionRead", "permissionWrite"} + permissions = {"read", "modify", "delete", "healthCheck", "pull", "push", "permissionRead", "permissionWrite"}, + custom = true, customGlobal = true ) @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "repositories") diff --git a/scm-core/src/main/java/sonia/scm/security/Permission.java b/scm-core/src/main/java/sonia/scm/security/Permission.java index 1b7c34f740..a7aa2798e7 100644 --- a/scm-core/src/main/java/sonia/scm/security/Permission.java +++ b/scm-core/src/main/java/sonia/scm/security/Permission.java @@ -6,7 +6,8 @@ import com.github.sdorra.ssp.StaticPermissions; @StaticPermissions( value = "permission", permissions = {}, - globalPermissions = {"list", "read", "assign"} + globalPermissions = {"list", "read", "assign"}, + custom = true, customGlobal = true ) public interface Permission extends PermissionObject { } diff --git a/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java b/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java index 0e3c06e32d..da11400140 100644 --- a/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java +++ b/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java @@ -45,7 +45,12 @@ import sonia.scm.user.User; import sonia.scm.user.UserManager; import sonia.scm.web.security.AdministrationContext; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; /** * Helper class for syncing realms. The class should simplify the creation of realms, which are syncing authenticated @@ -80,22 +85,107 @@ public final class SyncingRealmHelper { this.groupManager = groupManager; } - //~--- methods -------------------------------------------------------------- /** * Create {@link AuthenticationInfo} from user and groups. - * - * - * @param realm name of the realm - * @param user authenticated user - * @param groups groups of the authenticated user - * - * @return authentication info */ - public AuthenticationInfo createAuthenticationInfo(String realm, User user, - String... groups) { - return createAuthenticationInfo(realm, user, ImmutableList.copyOf(groups)); + public AuthenticationInfoBuilder.ForRealm authenticationInfo() { + return new AuthenticationInfoBuilder().new ForRealm(); } + public class AuthenticationInfoBuilder { + private String realm; + private User user; + private Collection groups; + private boolean external; + + private AuthenticationInfo build() { + return SyncingRealmHelper.this.createAuthenticationInfo(realm, user, groups, external); + } + + public class ForRealm { + private ForRealm() { + } + + /** + * Sets the realm. + * @param realm name of the realm + */ + public ForUser forRealm(String realm) { + AuthenticationInfoBuilder.this.realm = realm; + return AuthenticationInfoBuilder.this.new ForUser(); + } + } + + public class ForUser { + private ForUser() { + } + + /** + * Sets the user. + * @param user authenticated user + */ + public AuthenticationInfoBuilder.WithGroups andUser(User user) { + AuthenticationInfoBuilder.this.user = user; + return AuthenticationInfoBuilder.this.new WithGroups(); + } + } + + public class WithGroups { + private WithGroups() { + } + + /** + * Build the authentication info without groups. + * @return The complete {@link AuthenticationInfo} + */ + public AuthenticationInfo withoutGroups() { + return withGroups(emptyList()); + } + + /** + * Set the internal groups for the user. + * @param groups groups of the authenticated user + * @return The complete {@link AuthenticationInfo} + */ + public AuthenticationInfo withGroups(String... groups) { + return withGroups(asList(groups)); + } + + /** + * Set the internal groups for the user. + * @param groups groups of the authenticated user + * @return The complete {@link AuthenticationInfo} + */ + public AuthenticationInfo withGroups(Collection groups) { + AuthenticationInfoBuilder.this.groups = groups; + AuthenticationInfoBuilder.this.external = false; + return build(); + } + + /** + * Set the external groups for the user. + * @param groups external groups of the authenticated user + * @return The complete {@link AuthenticationInfo} + */ + public AuthenticationInfo withExternalGroups(String... groups) { + return withExternalGroups(asList(groups)); + } + + /** + * Set the external groups for the user. + * @param groups external groups of the authenticated user + * @return The complete {@link AuthenticationInfo} + */ + public AuthenticationInfo withExternalGroups(Collection groups) { + AuthenticationInfoBuilder.this.groups = groups; + AuthenticationInfoBuilder.this.external = true; + return build(); + } + } + } + + //~--- methods -------------------------------------------------------------- + /** * Create {@link AuthenticationInfo} from user and groups. * @@ -106,13 +196,13 @@ public final class SyncingRealmHelper { * * @return authentication info */ - public AuthenticationInfo createAuthenticationInfo(String realm, User user, - Collection groups) { + private AuthenticationInfo createAuthenticationInfo(String realm, User user, + Collection groups, boolean externalGroups) { SimplePrincipalCollection collection = new SimplePrincipalCollection(); collection.add(user.getId(), realm); collection.add(user, realm); - collection.add(new GroupNames(groups), realm); + collection.add(new GroupNames(groups, externalGroups), realm); return new SimpleAuthenticationInfo(collection, user.getPassword()); } @@ -161,6 +251,6 @@ public final class SyncingRealmHelper { } } - }); - } + }); } +} diff --git a/scm-core/src/main/java/sonia/scm/user/User.java b/scm-core/src/main/java/sonia/scm/user/User.java index cae383a402..3c185ae3b8 100644 --- a/scm-core/src/main/java/sonia/scm/user/User.java +++ b/scm-core/src/main/java/sonia/scm/user/User.java @@ -59,7 +59,9 @@ import java.security.Principal; @StaticPermissions( value = "user", globalPermissions = {"create", "list", "autocomplete"}, - permissions = {"read", "modify", "delete", "changePassword"}) + permissions = {"read", "modify", "delete", "changePassword"}, + custom = true, customGlobal = true +) @XmlRootElement(name = "users") @XmlAccessorType(XmlAccessType.FIELD) public class User extends BasicPropertiesAware implements Principal, ModelObject, PermissionObject, ReducedModelObject diff --git a/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java b/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java index 50bde53ae1..b8d6ae909e 100644 --- a/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java +++ b/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java @@ -37,6 +37,7 @@ package sonia.scm.security; import com.google.common.base.Throwables; import org.apache.shiro.authc.AuthenticationInfo; +import org.assertj.core.api.Assertions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -53,10 +54,14 @@ import sonia.scm.web.security.PrivilegedAction; import java.io.IOException; +import static java.util.Collections.singletonList; +import static org.assertj.core.util.Arrays.asList; import static org.hamcrest.Matchers.hasItem; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -106,25 +111,6 @@ public class SyncingRealmHelperTest { helper = new SyncingRealmHelper(ctx, userManager, groupManager); } - /** - * Tests {@link SyncingRealmHelper#createAuthenticationInfo(String, User, String...)}. - */ - @Test - public void testCreateAuthenticationInfo() { - User user = new User("tricia"); - AuthenticationInfo authInfo = helper.createAuthenticationInfo("unit-test", - user, "heartOfGold"); - - assertNotNull(authInfo); - assertEquals("tricia", authInfo.getPrincipals().getPrimaryPrincipal()); - assertThat(authInfo.getPrincipals().getRealmNames(), hasItem("unit-test")); - assertEquals(user, authInfo.getPrincipals().oneByType(User.class)); - - GroupNames groups = authInfo.getPrincipals().oneByType(GroupNames.class); - - assertThat(groups, hasItem("heartOfGold")); - } - /** * Tests {@link SyncingRealmHelper#store(Group)}. * @@ -198,4 +184,45 @@ public class SyncingRealmHelperTest { helper.store(user); verify(userManager, times(1)).modify(user); } + + @Test + public void builderShouldSetInternalGroups() { + AuthenticationInfo authenticationInfo = helper + .authenticationInfo() + .forRealm("unit-test") + .andUser(new User("ziltoid")) + .withGroups("internal"); + + GroupNames groupNames = authenticationInfo.getPrincipals().oneByType(GroupNames.class); + Assertions.assertThat(groupNames.getCollection()).containsOnly("internal"); + Assertions.assertThat(groupNames.isExternal()).isFalse(); + } + + @Test + public void builderShouldSetExternalGroups() { + AuthenticationInfo authenticationInfo = helper + .authenticationInfo() + .forRealm("unit-test") + .andUser(new User("ziltoid")) + .withExternalGroups("external"); + + GroupNames groupNames = authenticationInfo.getPrincipals().oneByType(GroupNames.class); + Assertions.assertThat(groupNames.getCollection()).containsOnly("external"); + Assertions.assertThat(groupNames.isExternal()).isTrue(); + } + + @Test + public void builderShouldSetValues() { + User user = new User("ziltoid"); + AuthenticationInfo authInfo = helper + .authenticationInfo() + .forRealm("unit-test") + .andUser(user) + .withoutGroups(); + + assertNotNull(authInfo); + assertEquals("ziltoid", authInfo.getPrincipals().getPrimaryPrincipal()); + assertThat(authInfo.getPrincipals().getRealmNames(), hasItem("unit-test")); + assertEquals(user, authInfo.getPrincipals().oneByType(User.class)); + } } diff --git a/scm-plugins/scm-git-plugin/package.json b/scm-plugins/scm-git-plugin/package.json index 1805f0665b..8d5ac3165b 100644 --- a/scm-plugins/scm-git-plugin/package.json +++ b/scm-plugins/scm-git-plugin/package.json @@ -12,6 +12,6 @@ "@scm-manager/ui-extensions": "^0.1.2" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.24" + "@scm-manager/ui-bundler": "^0.0.25" } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigInIndexResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigInIndexResource.java index a1120adda4..e6d7546ff4 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigInIndexResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigInIndexResource.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import sonia.scm.config.ConfigurationPermissions; import sonia.scm.plugin.Extension; +import sonia.scm.repository.GitConfig; import sonia.scm.web.JsonEnricherBase; import sonia.scm.web.JsonEnricherContext; @@ -26,7 +27,7 @@ public class GitConfigInIndexResource extends JsonEnricherBase { @Override public void enrich(JsonEnricherContext context) { - if (resultHasMediaType(INDEX, context) && ConfigurationPermissions.list().isPermitted()) { + if (resultHasMediaType(INDEX, context) && ConfigurationPermissions.read(GitConfig.PERMISSION).isPermitted()) { String gitConfigUrl = new LinkBuilder(scmPathInfoStore.get().get(), GitConfigResource.class) .method("get") .parameters() diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java index a9b9e25aca..95215607fe 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java @@ -43,6 +43,7 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.RemoteRefUpdate; +import org.eclipse.jgit.transport.ScmTransportProtocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.GitRepositoryHandler; @@ -62,7 +63,7 @@ public abstract class AbstractGitPushOrPullCommand extends AbstractGitCommand { /** Field description */ - private static final String SCHEME = "scm://"; + private static final String SCHEME = ScmTransportProtocol.NAME + "://"; /** * the logger for AbstractGitPushOrPullCommand @@ -167,7 +168,7 @@ public abstract class AbstractGitPushOrPullCommand extends AbstractGitCommand } else { - throw new IllegalArgumentException("repository or url is requiered"); + throw new IllegalArgumentException("repository or url is required"); } return url; diff --git a/scm-plugins/scm-git-plugin/src/main/resources/META-INF/scm/permissions.xml b/scm-plugins/scm-git-plugin/src/main/resources/META-INF/scm/permissions.xml index da11b5164a..4823cb5f4f 100644 --- a/scm-plugins/scm-git-plugin/src/main/resources/META-INF/scm/permissions.xml +++ b/scm-plugins/scm-git-plugin/src/main/resources/META-INF/scm/permissions.xml @@ -34,10 +34,10 @@ - configuration:read:git + configuration:read,write:git - configuration:write:git + repository:git:* diff --git a/scm-plugins/scm-git-plugin/src/main/resources/META-INF/scm/repository-permissions.xml b/scm-plugins/scm-git-plugin/src/main/resources/META-INF/scm/repository-permissions.xml new file mode 100644 index 0000000000..6c93929625 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/resources/META-INF/scm/repository-permissions.xml @@ -0,0 +1,7 @@ + + + git + + + + diff --git a/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json index cd88897e74..578d859c8e 100644 --- a/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json +++ b/scm-plugins/scm-git-plugin/src/main/resources/locales/de/plugins.json @@ -39,18 +39,28 @@ }, "permissions" : { "configuration": { - "read": { + "read,write": { "git": { - "displayName": "Git Konfiguration lesen", - "description": "Darf die git Konfiguration lesen." - } - }, - "write": { - "git": { - "displayName": "Git Konfiguration schreiben", + "displayName": "Git Konfiguration ändern", "description": "Darf die git Konfiguration verändern." } } + }, + "repository": { + "git": { + "*": { + "displayName": "Repository-spezifische Git Konfiguration ändern", + "description": "Darf die git Konfiguration für alle Repositories verändern." + } + } + } + }, + "verbs": { + "repository": { + "git": { + "displayName": "Git konfigurieren", + "description": "Darf die git Konfiguration für dieses Repository verändern." + } } } } diff --git a/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json index 551573fb72..bea0a08dc9 100644 --- a/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json +++ b/scm-plugins/scm-git-plugin/src/main/resources/locales/en/plugins.json @@ -39,18 +39,28 @@ }, "permissions" : { "configuration": { - "read": { + "read,write": { "git": { - "displayName": "Read git configuration", - "description": "May read the git configuration" - } - }, - "write": { - "git": { - "displayName": "Write git configuration", + "displayName": "Modify git configuration", "description": "May change the git configuration" } } + }, + "repository": { + "git": { + "*": { + "displayName": "Modify repository specific git configuration", + "description": "May change the git configuration for repositories" + } + } + } + }, + "verbs": { + "repository": { + "git": { + "displayName": "configure Git", + "description": "May change the git configuration for this repository" + } } } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigInIndexResourceTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigInIndexResourceTest.java index 665be19788..d6519a0013 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigInIndexResourceTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigInIndexResourceTest.java @@ -15,6 +15,7 @@ import java.net.URI; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; @SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini") public class GitConfigInIndexResourceTest { @@ -50,7 +51,7 @@ public class GitConfigInIndexResourceTest { gitConfigInIndexResource.enrich(context); - assertFalse(root.get("_links").iterator().hasNext()); + assertTrue(root.get("_links").iterator().hasNext()); } @Test diff --git a/scm-plugins/scm-git-plugin/yarn.lock b/scm-plugins/scm-git-plugin/yarn.lock index 64c47a247d..6438262437 100644 --- a/scm-plugins/scm-git-plugin/yarn.lock +++ b/scm-plugins/scm-git-plugin/yarn.lock @@ -707,9 +707,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.24": - version "0.0.24" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.24.tgz#034d5500c79b438c48d8f7ee985be07c4ea46d1e" +"@scm-manager/ui-bundler@^0.0.25": + version "0.0.25" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.25.tgz#1f65b3ff0ae81559a114c6a8d8cf43856cc6e166" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -750,7 +750,6 @@ "@scm-manager/ui-extensions@^0.1.2": version "0.1.2" resolved "https://registry.yarnpkg.com/@scm-manager/ui-extensions/-/ui-extensions-0.1.2.tgz#0689427ca45c8e4e045b5b9dbc89036f1d2c45fc" - integrity sha512-oIkXcc/VWssnK/yjWKC/Wnq5DZ01rArsz76n4X/0DT0hkGNIKmwk/Fdp7OoXiUEb7+aaPjUX1VvDqlTwCNKPmA== dependencies: react "^16.4.2" react-dom "^16.4.2" diff --git a/scm-plugins/scm-hg-plugin/package.json b/scm-plugins/scm-hg-plugin/package.json index 849d8a92cb..8d3926f0dd 100644 --- a/scm-plugins/scm-hg-plugin/package.json +++ b/scm-plugins/scm-hg-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.1.2" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.24" + "@scm-manager/ui-bundler": "^0.0.25" } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInIndexResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInIndexResource.java index 3de79b2f81..73c6e2e52f 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInIndexResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInIndexResource.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import sonia.scm.config.ConfigurationPermissions; import sonia.scm.plugin.Extension; +import sonia.scm.repository.HgConfig; import sonia.scm.web.JsonEnricherBase; import sonia.scm.web.JsonEnricherContext; @@ -26,7 +27,7 @@ public class HgConfigInIndexResource extends JsonEnricherBase { @Override public void enrich(JsonEnricherContext context) { - if (resultHasMediaType(INDEX, context) && ConfigurationPermissions.list().isPermitted()) { + if (resultHasMediaType(INDEX, context) && ConfigurationPermissions.read(HgConfig.PERMISSION).isPermitted()) { String hgConfigUrl = new LinkBuilder(scmPathInfoStore.get().get(), HgConfigResource.class) .method("get") .parameters() diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContext.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContext.java index cbf7804444..21e27af328 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContext.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContext.java @@ -35,13 +35,10 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- -import com.google.inject.servlet.RequestScoped; - /** * * @author Sebastian Sdorra */ -@RequestScoped public class HgContext { diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java index c86588fb27..33477e04b8 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextProvider.java @@ -35,13 +35,20 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- -import com.google.inject.Inject; -import com.google.inject.Provider; +import com.google.common.annotations.VisibleForTesting; +import com.google.inject.OutOfScopeException; +import com.google.inject.Provider; +import com.google.inject.ProvisionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.inject.Inject; + /** + * Injection provider for {@link HgContext}. + * This provider returns an instance {@link HgContext} from request scope, if no {@link HgContext} could be found in + * request scope (mostly because the scope is not available) a new {@link HgContext} gets returned. * * @author Sebastian Sdorra */ @@ -49,36 +56,50 @@ public class HgContextProvider implements Provider { /** - * the logger for HgContextProvider + * the LOG for HgContextProvider */ - private static final Logger logger = + private static final Logger LOG = LoggerFactory.getLogger(HgContextProvider.class); //~--- get methods ---------------------------------------------------------- - /** - * Method description - * - * - * @return - */ - @Override - public HgContext get() - { - HgContext ctx = context; + private Provider requestStoreProvider; - if (ctx == null) - { - ctx = new HgContext(); - logger.trace("context is null, we are probably out of request scope"); - } - - return ctx; + @Inject + public HgContextProvider(Provider requestStoreProvider) { + this.requestStoreProvider = requestStoreProvider; } - //~--- fields --------------------------------------------------------------- + @VisibleForTesting + public HgContextProvider() { + } - /** Field description */ - @Inject(optional = true) - private HgContext context; + @Override + public HgContext get() { + HgContext context = fetchContextFromRequest(); + if (context != null) { + LOG.trace("return HgContext from request store"); + return context; + } + LOG.trace("could not find context in request scope, returning new instance"); + return new HgContext(); + } + + private HgContext fetchContextFromRequest() { + try { + if (requestStoreProvider != null) { + return requestStoreProvider.get().get(); + } else { + LOG.trace("no request store provider defined, could not return context from request"); + return null; + } + } catch (ProvisionException ex) { + if (ex.getCause() instanceof OutOfScopeException) { + LOG.trace("we are currently out of request scope, failed to retrieve context"); + return null; + } else { + throw ex; + } + } + } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java new file mode 100644 index 0000000000..ff08c2fcd2 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgContextRequestStore.java @@ -0,0 +1,24 @@ +package sonia.scm.repository; + +import com.google.inject.servlet.RequestScoped; + +/** + * Holds an instance of {@link HgContext} in the request scope. + * + *

The problem seems to be that guice had multiple options for injecting HgContext. {@link HgContextProvider} + * bound via Module and {@link HgContext} bound void {@link RequestScoped} annotation. It looks like that Guice 4 + * injects randomly the one or the other, in SCMv1 (Guice 3) everything works as expected.

+ * + *

To fix the problem we have created this class annotated with {@link RequestScoped}, which holds an instance + * of {@link HgContext}. This way only the {@link HgContextProvider} is used for injection.

+ */ +@RequestScoped +public class HgContextRequestStore { + + private final HgContext context = new HgContext(); + + public HgContext get() { + return context; + } + +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java index ec35762de7..b6b085f3ac 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUtil.java @@ -134,6 +134,8 @@ public final class HgUtil repoConfiguration.setHgBin(handler.getConfig().getHgBinary()); + logger.debug("open hg repository {}: encoding: {}, pending: {}", directory, enc, pending); + return Repository.open(repoConfiguration, directory); } diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/META-INF/scm/permissions.xml b/scm-plugins/scm-hg-plugin/src/main/resources/META-INF/scm/permissions.xml index 205e8cc770..951bca4d76 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/META-INF/scm/permissions.xml +++ b/scm-plugins/scm-hg-plugin/src/main/resources/META-INF/scm/permissions.xml @@ -34,10 +34,10 @@ - configuration:read:hg + configuration:read,write:hg - configuration:write:hg + repository:hg:* diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/META-INF/scm/repository-permissions.xml b/scm-plugins/scm-hg-plugin/src/main/resources/META-INF/scm/repository-permissions.xml new file mode 100644 index 0000000000..3b83051504 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/resources/META-INF/scm/repository-permissions.xml @@ -0,0 +1,7 @@ + + + hg + + + + diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json index 37d6d4be2a..63a8cc8a98 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json +++ b/scm-plugins/scm-hg-plugin/src/main/resources/locales/de/plugins.json @@ -31,18 +31,28 @@ }, "permissions" : { "configuration": { - "read": { + "read,write": { "hg": { - "displayName": "Mercurial Konfiguration lesen", - "description": "Darf die Mercurial Konfiguration lesen" - } - }, - "write": { - "hg": { - "displayName": "Mercurial Konfiguration schreiben", + "displayName": "Mercurial Konfiguration ändern", "description": "Darf die Mercurial Konfiguration verändern" } } + }, + "repository": { + "hg": { + "*": { + "displayName": "Repository-spezifische Mercurial Konfiguration ändern", + "description": "Darf die Mercurial Konfiguration für alle Repositories verändern." + } + } + } + }, + "verbs": { + "repository": { + "hg": { + "displayName": "Mercurial konfigurieren", + "description": "Darf die Mercurial Konfiguration für dieses Repository verändern." + } } } } diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json index 61340ab9cf..a5d05d5796 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json +++ b/scm-plugins/scm-hg-plugin/src/main/resources/locales/en/plugins.json @@ -31,18 +31,28 @@ }, "permissions" : { "configuration": { - "read": { + "read,write": { "hg": { - "displayName": "Read Mercurial configuration", - "description": "May read the Mercurial configuration" - } - }, - "write": { - "hg": { - "displayName": "Write Mercurial configuration", + "displayName": "Modify Mercurial configuration", "description": "May change the Mercurial configuration" } } + }, + "repository": { + "hg": { + "*": { + "displayName": "Modify repository specific Mercurial configuration", + "description": "May change the Mercurial configuration for repositories" + } + } + } + }, + "verbs": { + "repository": { + "hg": { + "displayName": "configure Mercurial", + "description": "May change the Mercurial configuration for this repository" + } } } } diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/hgweb.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/hgweb.py index b14c8e8026..aeb5d6d588 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/hgweb.py +++ b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/hgweb.py @@ -46,8 +46,8 @@ u.setconfig('web', 'push_ssl', 'false') u.setconfig('web', 'allow_read', '*') u.setconfig('web', 'allow_push', '*') -u.setconfig('hooks', 'changegroup.scm', 'python:scmhooks.callback') -u.setconfig('hooks', 'pretxnchangegroup.scm', 'python:scmhooks.callback') +u.setconfig('hooks', 'changegroup.scm', 'python:scmhooks.postHook') +u.setconfig('hooks', 'pretxnchangegroup.scm', 'python:scmhooks.preHook') # pass SCM_HTTP_POST_ARGS to enable experimental httppostargs protocol of mercurial # SCM_HTTP_POST_ARGS is set by HgCGIServlet diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py index c64a63abfa..6ad8081512 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py +++ b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/python/scmhooks.py @@ -85,9 +85,7 @@ def callHookUrl(ui, repo, hooktype, node): ui.traceback() return abort -def callback(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs): - if pending != None: - pending() +def callback(ui, repo, hooktype, node=None): abort = True if node != None: if len(baseUrl) > 0: @@ -98,3 +96,28 @@ def callback(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs) else: ui.warn("changeset node is not available") return abort + +def preHook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs): + # older mercurial versions + if pending != None: + pending() + + # newer mercurial version + # we have to make in-memory changes visible to external process + # this does not happen automatically, because mercurial treat our hooks as internal hooks + # see hook.py at mercurial sources _exthook + try: + if repo is not None: + tr = repo.currenttransaction() + repo.dirstate.write(tr) + if tr and not tr.writepending(): + ui.warn("no pending write transaction found") + except AttributeError: + ui.debug("mercurial does not support currenttransation") + # do nothing + + return callback(ui, repo, hooktype, node) + +def postHook(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs): + return callback(ui, repo, hooktype, node) + diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInIndexResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInIndexResourceTest.java index 27ab74932c..d699a7b836 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInIndexResourceTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInIndexResourceTest.java @@ -15,6 +15,7 @@ import java.net.URI; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; @SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini") public class HgConfigInIndexResourceTest { @@ -50,7 +51,7 @@ public class HgConfigInIndexResourceTest { hgConfigInIndexResource.enrich(context); - assertFalse(root.get("_links").iterator().hasNext()); + assertTrue(root.get("_links").iterator().hasNext()); } @Test diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgContextProviderTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgContextProviderTest.java new file mode 100644 index 0000000000..de31e2b11e --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgContextProviderTest.java @@ -0,0 +1,87 @@ +package sonia.scm.repository; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.OutOfScopeException; +import com.google.inject.Provider; +import com.google.inject.ProvisionException; +import com.google.inject.Scope; +import com.google.inject.servlet.RequestScoped; +import com.google.inject.util.Providers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class HgContextProviderTest { + + @Mock + private Scope scope; + + @Test + void shouldThrowNonOutOfScopeProvisionExceptions() { + Provider provider = () -> { + throw new RuntimeException("something different"); + }; + + when(scope.scope(any(Key.class), any(Provider.class))).thenReturn(provider); + + Injector injector = Guice.createInjector(new HgContextModule(scope)); + + assertThrows(ProvisionException.class, () -> injector.getInstance(HgContext.class)); + } + + @Test + void shouldCreateANewInstanceIfOutOfRequestScope() { + Provider provider = () -> { + throw new OutOfScopeException("no request"); + }; + when(scope.scope(any(Key.class), any(Provider.class))).thenReturn(provider); + + Injector injector = Guice.createInjector(new HgContextModule(scope)); + + HgContext contextOne = injector.getInstance(HgContext.class); + HgContext contextTwo = injector.getInstance(HgContext.class); + + assertThat(contextOne).isNotSameAs(contextTwo); + } + + @Test + void shouldInjectFromRequestScope() { + HgContextRequestStore requestStore = new HgContextRequestStore(); + Provider provider = Providers.of(requestStore); + + when(scope.scope(any(Key.class), any(Provider.class))).thenReturn(provider); + + Injector injector = Guice.createInjector(new HgContextModule(scope)); + + HgContext contextOne = injector.getInstance(HgContext.class); + HgContext contextTwo = injector.getInstance(HgContext.class); + + assertThat(contextOne).isSameAs(contextTwo); + } + + private static class HgContextModule extends AbstractModule { + + private Scope scope; + + private HgContextModule(Scope scope) { + this.scope = scope; + } + + @Override + protected void configure() { + bindScope(RequestScoped.class, scope); + bind(HgContextRequestStore.class); + bind(HgContext.class).toProvider(HgContextProvider.class); + } + } +} diff --git a/scm-plugins/scm-hg-plugin/yarn.lock b/scm-plugins/scm-hg-plugin/yarn.lock index b47e6e6e32..47334b6e36 100644 --- a/scm-plugins/scm-hg-plugin/yarn.lock +++ b/scm-plugins/scm-hg-plugin/yarn.lock @@ -641,9 +641,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.24": - version "0.0.24" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.24.tgz#034d5500c79b438c48d8f7ee985be07c4ea46d1e" +"@scm-manager/ui-bundler@^0.0.25": + version "0.0.25" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.25.tgz#1f65b3ff0ae81559a114c6a8d8cf43856cc6e166" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -684,7 +684,6 @@ "@scm-manager/ui-extensions@^0.1.2": version "0.1.2" resolved "https://registry.yarnpkg.com/@scm-manager/ui-extensions/-/ui-extensions-0.1.2.tgz#0689427ca45c8e4e045b5b9dbc89036f1d2c45fc" - integrity sha512-oIkXcc/VWssnK/yjWKC/Wnq5DZ01rArsz76n4X/0DT0hkGNIKmwk/Fdp7OoXiUEb7+aaPjUX1VvDqlTwCNKPmA== dependencies: react "^16.4.2" react-dom "^16.4.2" diff --git a/scm-plugins/scm-svn-plugin/package.json b/scm-plugins/scm-svn-plugin/package.json index e5cddc0bba..8bbf204e2f 100644 --- a/scm-plugins/scm-svn-plugin/package.json +++ b/scm-plugins/scm-svn-plugin/package.json @@ -9,6 +9,6 @@ "@scm-manager/ui-extensions": "^0.1.2" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.24" + "@scm-manager/ui-bundler": "^0.0.25" } } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigInIndexResource.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigInIndexResource.java index 5ee1de3169..918d38a346 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigInIndexResource.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigInIndexResource.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import sonia.scm.config.ConfigurationPermissions; import sonia.scm.plugin.Extension; +import sonia.scm.repository.SvnConfig; import sonia.scm.web.JsonEnricherBase; import sonia.scm.web.JsonEnricherContext; @@ -26,7 +27,7 @@ public class SvnConfigInIndexResource extends JsonEnricherBase { @Override public void enrich(JsonEnricherContext context) { - if (resultHasMediaType(INDEX, context) && ConfigurationPermissions.list().isPermitted()) { + if (resultHasMediaType(INDEX, context) && ConfigurationPermissions.read(SvnConfig.PERMISSION).isPermitted()) { String svnConfigUrl = new LinkBuilder(scmPathInfoStore.get().get(), SvnConfigResource.class) .method("get") .parameters() diff --git a/scm-plugins/scm-svn-plugin/src/main/resources/META-INF/scm/permissions.xml b/scm-plugins/scm-svn-plugin/src/main/resources/META-INF/scm/permissions.xml index 3da3526f93..602b1606e6 100644 --- a/scm-plugins/scm-svn-plugin/src/main/resources/META-INF/scm/permissions.xml +++ b/scm-plugins/scm-svn-plugin/src/main/resources/META-INF/scm/permissions.xml @@ -34,10 +34,10 @@ - configuration:read:svn + configuration:read,write:svn - configuration:write:svn + repository:svn:* diff --git a/scm-plugins/scm-svn-plugin/src/main/resources/META-INF/scm/repository-permissions.xml b/scm-plugins/scm-svn-plugin/src/main/resources/META-INF/scm/repository-permissions.xml new file mode 100644 index 0000000000..7c7cd48b79 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/resources/META-INF/scm/repository-permissions.xml @@ -0,0 +1,7 @@ + + + svn + + + + diff --git a/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json b/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json index 58a18482b2..1b27a23564 100644 --- a/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json +++ b/scm-plugins/scm-svn-plugin/src/main/resources/locales/de/plugins.json @@ -25,18 +25,28 @@ }, "permissions": { "configuration": { - "read": { + "read,write": { "svn": { - "displayName": "Subversion Konfiguration lesen", - "description": "Darf die Subversion Konfiguration lesen" - } - }, - "write": { - "svn": { - "displayName": "Subversion Konfiguration schreiben", + "displayName": "Subversion Konfiguration ändern", "description": "Darf die Subversion Konfiguration verändern" } } + }, + "repository": { + "svn": { + "*": { + "displayName": "Repository-spezifische Subversion Konfiguration ändern", + "description": "Darf die Subversion Konfiguration für alle Repositories verändern." + } + } + } + }, + "verbs": { + "repository": { + "svn": { + "displayName": "Subversion konfigurieren", + "description": "Darf die Subversion Konfiguration für dieses Repository verändern." + } } } } diff --git a/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json b/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json index a796027afc..0d487e1f3d 100644 --- a/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json +++ b/scm-plugins/scm-svn-plugin/src/main/resources/locales/en/plugins.json @@ -25,18 +25,28 @@ }, "permissions": { "configuration": { - "read": { + "read,write": { "svn": { - "displayName": "Read Subversion configuration", - "description": "May read the Subversion configuration" + "displayName": "Modify Subversion configuration", + "description": "May modify the Subversion configuration" } - }, - "write": { - "svn": { - "displayName": "Write Subversion configuration", - "description": "May change the Subversion configuration" + } + }, + "repository": { + "svn": { + "*": { + "displayName": "Modify repository specific Subversion configuration", + "description": "May change the Subversion configuration for repositories" } } } + }, + "verbs": { + "repository": { + "svn": { + "displayName": "configure Subversion", + "description": "May change the Subversion configuration for this repository" + } + } } } diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigInIndexResourceTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigInIndexResourceTest.java index 8b87b57c6c..5d4fa36fe6 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigInIndexResourceTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigInIndexResourceTest.java @@ -15,6 +15,7 @@ import java.net.URI; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; @SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini") public class SvnConfigInIndexResourceTest { @@ -50,7 +51,7 @@ public class SvnConfigInIndexResourceTest { svnConfigInIndexResource.enrich(context); - assertFalse(root.get("_links").iterator().hasNext()); + assertTrue(root.get("_links").iterator().hasNext()); } @Test diff --git a/scm-plugins/scm-svn-plugin/yarn.lock b/scm-plugins/scm-svn-plugin/yarn.lock index b47e6e6e32..47334b6e36 100644 --- a/scm-plugins/scm-svn-plugin/yarn.lock +++ b/scm-plugins/scm-svn-plugin/yarn.lock @@ -641,9 +641,9 @@ version "0.0.2" resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85" -"@scm-manager/ui-bundler@^0.0.24": - version "0.0.24" - resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.24.tgz#034d5500c79b438c48d8f7ee985be07c4ea46d1e" +"@scm-manager/ui-bundler@^0.0.25": + version "0.0.25" + resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.25.tgz#1f65b3ff0ae81559a114c6a8d8cf43856cc6e166" dependencies: "@babel/core" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" @@ -684,7 +684,6 @@ "@scm-manager/ui-extensions@^0.1.2": version "0.1.2" resolved "https://registry.yarnpkg.com/@scm-manager/ui-extensions/-/ui-extensions-0.1.2.tgz#0689427ca45c8e4e045b5b9dbc89036f1d2c45fc" - integrity sha512-oIkXcc/VWssnK/yjWKC/Wnq5DZ01rArsz76n4X/0DT0hkGNIKmwk/Fdp7OoXiUEb7+aaPjUX1VvDqlTwCNKPmA== dependencies: react "^16.4.2" react-dom "^16.4.2" diff --git a/scm-ui-components/packages/ui-components/package.json b/scm-ui-components/packages/ui-components/package.json index bb8e5c738e..80b83b0265 100644 --- a/scm-ui-components/packages/ui-components/package.json +++ b/scm-ui-components/packages/ui-components/package.json @@ -14,7 +14,7 @@ "eslint-fix": "eslint src --fix" }, "devDependencies": { - "@scm-manager/ui-bundler": "^0.0.24", + "@scm-manager/ui-bundler": "^0.0.25", "create-index": "^2.3.0", "enzyme": "^3.5.0", "enzyme-adapter-react-16": "^1.3.1", @@ -31,12 +31,15 @@ "classnames": "^2.2.6", "moment": "^2.22.2", "react": "^16.5.2", + "react-diff-view": "^1.8.1", "react-dom": "^16.5.2", "react-i18next": "^7.11.0", "react-jss": "^8.6.1", "react-router-dom": "^4.3.1", - "react-select": "^2.1.2", - "diff2html": "^2.5.0" + "react-select": "^2.1.2" + }, + "resolutions": { + "gitdiff-parser": "https://github.com/cloudogu/gitdiff-parser#3a72da4a8e3d9bfb4b9e01a43e85628c19f26cc4" }, "browserify": { "transform": [ diff --git a/scm-ui-components/packages/ui-components/src/Help.js b/scm-ui-components/packages/ui-components/src/Help.js index 047f79244f..9cb37e722d 100644 --- a/scm-ui-components/packages/ui-components/src/Help.js +++ b/scm-ui-components/packages/ui-components/src/Help.js @@ -7,7 +7,8 @@ import HelpIcon from './HelpIcon'; const styles = { tooltip: { display: "inline-block", - paddingLeft: "3px" + paddingLeft: "3px", + position: "absolute" } }; diff --git a/scm-ui-components/packages/ui-components/src/buttons/CreateButton.js b/scm-ui-components/packages/ui-components/src/buttons/CreateButton.js index 3df3e78680..155669754c 100644 --- a/scm-ui-components/packages/ui-components/src/buttons/CreateButton.js +++ b/scm-ui-components/packages/ui-components/src/buttons/CreateButton.js @@ -2,8 +2,8 @@ import React from "react"; import injectSheet from "react-jss"; import { type ButtonProps } from "./Button"; -import SubmitButton from "./SubmitButton"; import classNames from "classnames"; +import Button from "./Button"; const styles = { spacing: { @@ -19,7 +19,7 @@ class CreateButton extends React.Component { const { classes } = this.props; return (
- +
); } diff --git a/scm-ui-components/packages/ui-components/src/buttons/SubmitButton.js b/scm-ui-components/packages/ui-components/src/buttons/SubmitButton.js index 1a8604834b..0f03d850b2 100644 --- a/scm-ui-components/packages/ui-components/src/buttons/SubmitButton.js +++ b/scm-ui-components/packages/ui-components/src/buttons/SubmitButton.js @@ -4,7 +4,20 @@ import Button, { type ButtonProps } from "./Button"; class SubmitButton extends React.Component { render() { - return