diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryRoleManager.java b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryRoleManager.java new file mode 100644 index 0000000000..1db0065e8b --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryRoleManager.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.repository; + +import sonia.scm.HandlerEventType; +import sonia.scm.event.ScmEventBus; + +/** + * Abstract base class for {@link RepositoryRoleManager} implementations. This class + * implements the listener methods of the {@link RepositoryRoleManager} interface. + */ +public abstract class AbstractRepositoryRoleManager implements RepositoryRoleManager { + + /** + * Send a {@link RepositoryRoleEvent} to the {@link ScmEventBus}. + * + * @param event type of change event + * @param repositoryRole repositoryRole that has changed + * @param oldRepositoryRole old repositoryRole + */ + protected void fireEvent(HandlerEventType event, RepositoryRole repositoryRole, RepositoryRole oldRepositoryRole) + { + fireEvent(new RepositoryRoleModificationEvent(event, repositoryRole, oldRepositoryRole)); + } + + /** + * Creates a new {@link RepositoryRoleEvent} and calls {@link #fireEvent(RepositoryRoleEvent)}. + * + * @param repositoryRole repositoryRole that has changed + * @param event type of change event + */ + protected void fireEvent(HandlerEventType event, RepositoryRole repositoryRole) + { + fireEvent(new RepositoryRoleEvent(event, repositoryRole)); + } + + /** + * Send a {@link RepositoryRoleEvent} to the {@link ScmEventBus}. + * + * @param event repositoryRole event + * @since 1.48 + */ + protected void fireEvent(RepositoryRoleEvent event) + { + ScmEventBus.getInstance().post(event); + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/RemoveDeletedRepositoryRole.java b/scm-core/src/main/java/sonia/scm/repository/RemoveDeletedRepositoryRole.java new file mode 100644 index 0000000000..25564dca83 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/RemoveDeletedRepositoryRole.java @@ -0,0 +1,48 @@ +package sonia.scm.repository; + +import com.github.legman.Subscribe; +import sonia.scm.EagerSingleton; +import sonia.scm.plugin.Extension; + +import javax.inject.Inject; + +import java.util.Optional; + +import static sonia.scm.HandlerEventType.DELETE; + +@EagerSingleton +@Extension +public class RemoveDeletedRepositoryRole { + + private final RepositoryManager repositoryManager; + + @Inject + public RemoveDeletedRepositoryRole(RepositoryManager repositoryManager) { + this.repositoryManager = repositoryManager; + } + + @Subscribe + void handle(RepositoryRoleEvent event) { + if (event.getEventType() == DELETE) { + repositoryManager.getAll() + .forEach(repository -> check(repository, event.getItem())); + } + } + + private void check(Repository repository, RepositoryRole role) { + findPermission(repository, role) + .ifPresent(permission -> removeFromPermissions(repository, permission)); + } + + private Optional findPermission(Repository repository, RepositoryRole item) { + return repository.getPermissions() + .stream() + .filter(repositoryPermission -> item.getName().equals(repositoryPermission.getRole())) + .findFirst(); + } + + private void removeFromPermissions(Repository repository, RepositoryPermission permission) { + repository.removePermission(permission); + repositoryManager.modify(repository); + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java index 79236ab679..3099c1f74b 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java @@ -50,6 +50,7 @@ import java.util.LinkedHashSet; import java.util.Set; import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; import static java.util.Collections.unmodifiableSet; //~--- JDK imports ------------------------------------------------------------ @@ -73,6 +74,7 @@ public class RepositoryPermission implements PermissionObject, Serializable private String name; @XmlElement(name = "verb") private Set verbs; + private String role; /** * This constructor exists for mapstruct and JAXB, only -- do not use this in "normal" code. @@ -87,6 +89,15 @@ public class RepositoryPermission implements PermissionObject, Serializable { this.name = name; this.verbs = new LinkedHashSet<>(verbs); + this.role = null; + this.groupPermission = groupPermission; + } + + public RepositoryPermission(String name, String role, boolean groupPermission) + { + this.name = name; + this.verbs = emptySet(); + this.role = role; this.groupPermission = groupPermission; } @@ -116,8 +127,9 @@ public class RepositoryPermission implements PermissionObject, Serializable final RepositoryPermission other = (RepositoryPermission) obj; return Objects.equal(name, other.name) - && verbs.containsAll(other.verbs) && verbs.size() == other.verbs.size() + && verbs.containsAll(other.verbs) + && Objects.equal(role, other.role) && Objects.equal(groupPermission, other.groupPermission); } @@ -132,7 +144,7 @@ public class RepositoryPermission implements PermissionObject, Serializable { // Normally we do not have a log of repository permissions having the same size of verbs, but different content. // Therefore we do not use the verbs themselves for the hash code but only the number of verbs. - return Objects.hashCode(name, verbs == null? -1: verbs.size(), groupPermission); + return Objects.hashCode(name, verbs == null? -1: verbs.size(), role, groupPermission); } @@ -142,6 +154,7 @@ public class RepositoryPermission implements PermissionObject, Serializable //J- return MoreObjects.toStringHelper(this) .add("name", name) + .add("role", role) .add("verbs", verbs) .add("groupPermission", groupPermission) .toString(); @@ -173,6 +186,16 @@ public class RepositoryPermission implements PermissionObject, Serializable return verbs == null ? emptyList() : Collections.unmodifiableSet(verbs); } + /** + * Returns the role of the permission. + * + * + * @return role of the permission + */ + public String getRole() { + return role; + } + /** * Returns true if the permission is a permission which affects a group. * @@ -192,7 +215,8 @@ public class RepositoryPermission implements PermissionObject, Serializable * @throws IllegalStateException when modified after the value has been set once. * * @deprecated Do not use this for "normal" code. - * Use {@link RepositoryPermission#RepositoryPermission(String, Collection, boolean)} instead. + * Use {@link RepositoryPermission#RepositoryPermission(String, Collection, boolean)} + * or {@link RepositoryPermission#RepositoryPermission(String, String, boolean)} instead. */ @Deprecated public void setGroupPermission(boolean groupPermission) @@ -208,7 +232,8 @@ public class RepositoryPermission implements PermissionObject, Serializable * @throws IllegalStateException when modified after the value has been set once. * * @deprecated Do not use this for "normal" code. - * Use {@link RepositoryPermission#RepositoryPermission(String, Collection, boolean)} instead. + * Use {@link RepositoryPermission#RepositoryPermission(String, Collection, boolean)} + * or {@link RepositoryPermission#RepositoryPermission(String, String, boolean)} instead. */ @Deprecated public void setName(String name) @@ -219,6 +244,22 @@ public class RepositoryPermission implements PermissionObject, Serializable this.name = name; } + /** + * Use this for creation only. This will throw an {@link IllegalStateException} when modified. + * @throws IllegalStateException when modified after the value has been set once. + * + * @deprecated Do not use this for "normal" code. + * Use {@link RepositoryPermission#RepositoryPermission(String, String, boolean)} instead. + */ + @Deprecated + public void setRole(String role) + { + if (this.role != null) { + throw new IllegalStateException(REPOSITORY_MODIFIED_EXCEPTION_TEXT); + } + this.role = role; + } + /** * Use this for creation only. This will throw an {@link IllegalStateException} when modified. * @throws IllegalStateException when modified after the value has been set once. @@ -232,6 +273,6 @@ public class RepositoryPermission implements PermissionObject, Serializable if (this.verbs != null) { throw new IllegalStateException(REPOSITORY_MODIFIED_EXCEPTION_TEXT); } - this.verbs = unmodifiableSet(new LinkedHashSet<>(verbs)); + this.verbs = verbs == null? emptySet(): unmodifiableSet(new LinkedHashSet<>(verbs)); } } diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryRole.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryRole.java new file mode 100644 index 0000000000..1f8f638cb8 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryRole.java @@ -0,0 +1,230 @@ +/* + Copyright (c) 2010, Sebastian Sdorra + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. Neither the name of SCM-Manager; nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + http://bitbucket.org/sdorra/scm-manager + + */ + + + +package sonia.scm.repository; + +import com.github.sdorra.ssp.PermissionObject; +import com.github.sdorra.ssp.StaticPermissions; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.base.Strings; +import sonia.scm.ModelObject; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.unmodifiableSet; + +/** + * Custom role with specific permissions related to {@link Repository}. + * This object should be immutable, but could not be due to mapstruct. + */ +@StaticPermissions(value = "repositoryRole", permissions = {}, globalPermissions = {"read", "modify"}) +@XmlRootElement(name = "roles") +@XmlAccessorType(XmlAccessType.FIELD) +public class RepositoryRole implements ModelObject, PermissionObject { + + private static final long serialVersionUID = -723588336073192740L; + + private static final String REPOSITORY_MODIFIED_EXCEPTION_TEXT = "roles must not be modified"; + + private String name; + @XmlElement(name = "verb") + private Set verbs; + + private Long creationDate; + private Long lastModified; + private String type; + + /** + * This constructor exists for mapstruct and JAXB, only -- do not use this in "normal" code. + * + * @deprecated Do not use this for "normal" code. + * Use {@link RepositoryRole#RepositoryRole(String, Collection, String)} instead. + */ + @Deprecated + public RepositoryRole() {} + + public RepositoryRole(String name, Collection verbs, String type) { + this.name = name; + this.verbs = new LinkedHashSet<>(verbs); + this.type = type; + } + + /** + * Returns true if the {@link RepositoryRole} is the same as the obj argument. + * + * + * @param obj the reference object with which to compare + * + * @return true if the {@link RepositoryRole} is the same as the obj argument + */ + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + + if (getClass() != obj.getClass()) { + return false; + } + + final RepositoryRole other = (RepositoryRole) obj; + + return Objects.equal(name, other.name) + && verbs.size() == other.verbs.size() + && verbs.containsAll(other.verbs); + } + + /** + * Returns the hash code value for the {@link RepositoryRole}. + * + * + * @return the hash code value for the {@link RepositoryRole} + */ + @Override + public int hashCode() + { + // Normally we do not have a log of repository permissions having the same size of verbs, but different content. + // Therefore we do not use the verbs themselves for the hash code but only the number of verbs. + return Objects.hashCode(name, verbs == null? -1: verbs.size()); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("name", name) + .add("verbs", verbs) + .toString(); + } + + public String getName() { + return name; + } + + /** + * Returns the verb of the role. + */ + public Collection getVerbs() { + return verbs == null ? emptyList() : Collections.unmodifiableSet(verbs); + } + + @Override + public String getId() { + return name; + } + + @Override + public void setLastModified(Long timestamp) { + this.lastModified = timestamp; + } + + @Override + public Long getCreationDate() { + return creationDate; + } + + @Override + public void setCreationDate(Long timestamp) { + this.creationDate = timestamp; + } + + @Override + public Long getLastModified() { + return lastModified; + } + + @Override + public String getType() { + return type; + } + + public void setType(String type) { + if (this.type != null) { + throw new IllegalStateException(REPOSITORY_MODIFIED_EXCEPTION_TEXT); + } + this.type = type; + } + + @Override + public boolean isValid() { + return !Strings.isNullOrEmpty(name) && !verbs.isEmpty(); + } + + /** + * Use this for creation only. This will throw an {@link IllegalStateException} when modified. + * @throws IllegalStateException when modified after the value has been set once. + * + * @deprecated Do not use this for "normal" code. + * Use {@link RepositoryRole#RepositoryRole(String, Collection, String)} instead. + */ + @Deprecated + public void setName(String name) { + if (this.name != null) { + throw new IllegalStateException(REPOSITORY_MODIFIED_EXCEPTION_TEXT); + } + this.name = name; + } + + /** + * Use this for creation only. This will throw an {@link IllegalStateException} when modified. + * @throws IllegalStateException when modified after the value has been set once. + * + * @deprecated Do not use this for "normal" code. + * Use {@link RepositoryRole#RepositoryRole(String, Collection, String)} instead. + */ + @Deprecated + public void setVerbs(Collection verbs) { + if (this.verbs != null) { + throw new IllegalStateException(REPOSITORY_MODIFIED_EXCEPTION_TEXT); + } + this.verbs = verbs == null? emptySet(): unmodifiableSet(new LinkedHashSet<>(verbs)); + } + + @Override + public RepositoryRole clone() { + try { + return (RepositoryRole) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleDAO.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleDAO.java new file mode 100644 index 0000000000..3d7e53b3a2 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleDAO.java @@ -0,0 +1,10 @@ +package sonia.scm.repository; + +import sonia.scm.GenericDAO; + +import java.util.List; + +public interface RepositoryRoleDAO extends GenericDAO { + @Override + List getAll(); +} diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleEvent.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleEvent.java new file mode 100644 index 0000000000..fcd21bfbcd --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleEvent.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.repository; + +import sonia.scm.HandlerEventType; +import sonia.scm.event.AbstractHandlerEvent; +import sonia.scm.event.Event; + +/** + * The RepositoryRoleEvent is fired if a repository role object changes. + * @since 2.0 + */ +@Event +public class RepositoryRoleEvent extends AbstractHandlerEvent { + + /** + * Constructs a new repositoryRole event. + * + * + * @param eventType event type + * @param repositoryRole changed repositoryRole + */ + public RepositoryRoleEvent(HandlerEventType eventType, RepositoryRole repositoryRole) { + super(eventType, repositoryRole); + } + + /** + * Constructs a new repositoryRole event. + * + * + * @param eventType type of the event + * @param repositoryRole changed repositoryRole + * @param oldRepositoryRole old repositoryRole + */ + public RepositoryRoleEvent(HandlerEventType eventType, RepositoryRole repositoryRole, RepositoryRole oldRepositoryRole) { + super(eventType, repositoryRole, oldRepositoryRole); + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleManager.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleManager.java new file mode 100644 index 0000000000..c7e1971110 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleManager.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.repository; + +import sonia.scm.Manager; +import sonia.scm.search.Searchable; + +/** + * The central class for managing {@link RepositoryRole} objects. + * This class is a singleton and is available via injection. + */ +public interface RepositoryRoleManager extends Manager { +} diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleModificationEvent.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleModificationEvent.java new file mode 100644 index 0000000000..eabeb26b2e --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleModificationEvent.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.repository; + +import sonia.scm.HandlerEventType; +import sonia.scm.ModificationHandlerEvent; +import sonia.scm.event.Event; + +/** + * Event which is fired whenever a repository role is modified. + * + * @since 2.0 + */ +@Event +public class RepositoryRoleModificationEvent extends RepositoryRoleEvent implements ModificationHandlerEvent +{ + + private final RepositoryRole itemBeforeModification; + + /** + * Constructs a new {@link RepositoryRoleModificationEvent}. + * + * @param eventType type of event + * @param item changed repository role + * @param itemBeforeModification changed repository role before it was modified + */ + public RepositoryRoleModificationEvent(HandlerEventType eventType, RepositoryRole item, RepositoryRole itemBeforeModification) + { + super(eventType, item); + this.itemBeforeModification = itemBeforeModification; + } + + @Override + public RepositoryRole getItemBeforeModification() + { + return itemBeforeModification; + } + +} diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index 4dcb2f1d96..c9696b0641 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -34,7 +34,7 @@ public class VndMediaType { public static final String REPOSITORY_COLLECTION = PREFIX + "repositoryCollection" + SUFFIX; public static final String BRANCH_COLLECTION = PREFIX + "branchCollection" + SUFFIX; public static final String CONFIG = PREFIX + "config" + SUFFIX; - public static final String REPOSITORY_PERMISSION_COLLECTION = PREFIX + "repositoryPermissionCollection" + SUFFIX; + public static final String REPOSITORY_VERB_COLLECTION = PREFIX + "repositoryVerbCollection" + SUFFIX; public static final String REPOSITORY_TYPE_COLLECTION = PREFIX + "repositoryTypeCollection" + SUFFIX; public static final String REPOSITORY_TYPE = PREFIX + "repositoryType" + SUFFIX; public static final String UI_PLUGIN = PREFIX + "uiPlugin" + SUFFIX; @@ -53,6 +53,9 @@ public class VndMediaType { public static final String SOURCE = PREFIX + "source" + SUFFIX; public static final String ERROR_TYPE = PREFIX + "error" + SUFFIX; + public static final String REPOSITORY_ROLE = PREFIX + "repositoryRole" + SUFFIX; + public static final String REPOSITORY_ROLE_COLLECTION = PREFIX + "repositoryRoleCollection" + SUFFIX; + private VndMediaType() { } diff --git a/scm-core/src/test/java/sonia/scm/repository/RemoveDeletedRepositoryRoleTest.java b/scm-core/src/test/java/sonia/scm/repository/RemoveDeletedRepositoryRoleTest.java new file mode 100644 index 0000000000..f2f88f9ea7 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/repository/RemoveDeletedRepositoryRoleTest.java @@ -0,0 +1,91 @@ +package sonia.scm.repository; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import sonia.scm.HandlerEventType; + +import java.util.Arrays; +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.quality.Strictness.LENIENT; +import static sonia.scm.HandlerEventType.DELETE; + +@ExtendWith(MockitoExtension.class) +class RemoveDeletedRepositoryRoleTest { + + static final Repository REPOSITORY = createRepositoryWithRoles("with", "deleted", "kept"); + + @Mock + RepositoryManager manager; + @Captor + ArgumentCaptor modifyCaptor; + + private RemoveDeletedRepositoryRole removeDeletedRepositoryRole; + + @BeforeEach + void init() { + removeDeletedRepositoryRole = new RemoveDeletedRepositoryRole(manager); + doNothing().when(manager).modify(modifyCaptor.capture()); + } + + @Test + void shouldRemoveDeletedPermission() { + when(manager.getAll()).thenReturn(Collections.singletonList(REPOSITORY)); + + removeDeletedRepositoryRole.handle(new RepositoryRoleEvent(DELETE, createRole("deleted"))); + + verify(manager).modify(any()); + Assertions.assertThat(modifyCaptor.getValue().getPermissions()) + .containsExactly(createPermission("kept")); + } + + @Test + @MockitoSettings(strictness = LENIENT) + void shouldDoNothingForEventsWithUnusedRole() { + when(manager.getAll()).thenReturn(Collections.singletonList(REPOSITORY)); + + removeDeletedRepositoryRole.handle(new RepositoryRoleEvent(DELETE, createRole("unused"))); + + verify(manager, never()).modify(any()); + } + + @Test + @MockitoSettings(strictness = LENIENT) + void shouldDoNothingForEventsOtherThanDelete() { + when(manager.getAll()).thenReturn(Collections.singletonList(REPOSITORY)); + + Arrays.stream(HandlerEventType.values()) + .filter(type -> type != DELETE) + .forEach( + type -> removeDeletedRepositoryRole.handle(new RepositoryRoleEvent(type, createRole("deleted"))) + ); + + verify(manager, never()).modify(any()); + } + + private RepositoryRole createRole(String name) { + return new RepositoryRole(name, Collections.singleton("x"), "x"); + } + + static Repository createRepositoryWithRoles(String name, String... roles) { + Repository repository = new Repository("x", "git", "space", name); + Arrays.stream(roles).forEach(role -> repository.addPermission(createPermission(role))); + return repository; + } + + private static RepositoryPermission createPermission(String role) { + return new RepositoryPermission("user", role, false); + } +} diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryRoleDAO.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryRoleDAO.java new file mode 100644 index 0000000000..0eb860a134 --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryRoleDAO.java @@ -0,0 +1,42 @@ +package sonia.scm.repository.xml; + +import com.google.inject.Inject; +import sonia.scm.repository.RepositoryRole; +import sonia.scm.repository.RepositoryRoleDAO; +import sonia.scm.store.ConfigurationStoreFactory; +import sonia.scm.xml.AbstractXmlDAO; + +import javax.inject.Singleton; +import java.util.List; + +@Singleton +public class XmlRepositoryRoleDAO extends AbstractXmlDAO + implements RepositoryRoleDAO { + + public static final String STORE_NAME = "repositoryRoles"; + + @Inject + public XmlRepositoryRoleDAO(ConfigurationStoreFactory storeFactory) { + super(storeFactory + .withType(XmlRepositoryRoleDatabase.class) + .withName(STORE_NAME) + .build()); + } + + @Override + protected RepositoryRole clone(RepositoryRole role) + { + return role.clone(); + } + + @Override + protected XmlRepositoryRoleDatabase createNewDatabase() + { + return new XmlRepositoryRoleDatabase(); + } + + @Override + public List getAll() { + return (List) super.getAll(); + } +} diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryRoleDatabase.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryRoleDatabase.java new file mode 100644 index 0000000000..c7665316e2 --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryRoleDatabase.java @@ -0,0 +1,77 @@ +package sonia.scm.repository.xml; + +import sonia.scm.repository.RepositoryRole; +import sonia.scm.xml.XmlDatabase; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +@XmlRootElement(name = "user-db") +@XmlAccessorType(XmlAccessType.FIELD) +public class XmlRepositoryRoleDatabase implements XmlDatabase { + + private Long creationTime; + private Long lastModified; + + @XmlJavaTypeAdapter(XmlRepositoryRoleMapAdapter.class) + @XmlElement(name = "roles") + private Map roleMap = new LinkedHashMap<>(); + + public XmlRepositoryRoleDatabase() { + long c = System.currentTimeMillis(); + + creationTime = c; + lastModified = c; + } + + @Override + public void add(RepositoryRole role) { + roleMap.put(role.getName(), role); + } + + @Override + public boolean contains(String name) { + return roleMap.containsKey(name); + } + + @Override + public RepositoryRole remove(String name) { + return roleMap.remove(name); + } + + @Override + public Collection values() { + return roleMap.values(); + } + + @Override + public RepositoryRole get(String name) { + return roleMap.get(name); + } + + @Override + public long getCreationTime() { + return creationTime; + } + + @Override + public long getLastModified() { + return lastModified; + } + + @Override + public void setCreationTime(long creationTime) { + this.creationTime = creationTime; + } + + @Override + public void setLastModified(long lastModified) { + this.lastModified = lastModified; + } +} diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryRoleList.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryRoleList.java new file mode 100644 index 0000000000..7910d4300a --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryRoleList.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.repository.xml; + +import sonia.scm.repository.RepositoryRole; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; + +@XmlRootElement(name = "roles") +@XmlAccessorType(XmlAccessType.FIELD) +public class XmlRepositoryRoleList implements Iterable { + + public XmlRepositoryRoleList() {} + + public XmlRepositoryRoleList(Map roleMap) { + this.roles = new LinkedList(roleMap.values()); + } + + @Override + public Iterator iterator() + { + return roles.iterator(); + } + + public LinkedList getRoles() + { + return roles; + } + + public void setRoles(LinkedList roles) + { + this.roles = roles; + } + + @XmlElement(name = "role") + private LinkedList roles; +} diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryRoleMapAdapter.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryRoleMapAdapter.java new file mode 100644 index 0000000000..959eff331a --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryRoleMapAdapter.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.repository.xml; + +import sonia.scm.repository.RepositoryRole; + +import javax.xml.bind.annotation.adapters.XmlAdapter; +import java.util.LinkedHashMap; +import java.util.Map; + +public class XmlRepositoryRoleMapAdapter + extends XmlAdapter> { + + @Override + public XmlRepositoryRoleList marshal(Map roleMap) { + return new XmlRepositoryRoleList(roleMap); + } + + @Override + public Map unmarshal(XmlRepositoryRoleList roles) { + Map roleMap = new LinkedHashMap<>(); + + for (RepositoryRole role : roles) { + roleMap.put(role.getName(), role); + } + + return roleMap; + } +} diff --git a/scm-it/src/test/java/sonia/scm/it/RoleITCase.java b/scm-it/src/test/java/sonia/scm/it/RoleITCase.java new file mode 100644 index 0000000000..331a251d23 --- /dev/null +++ b/scm-it/src/test/java/sonia/scm/it/RoleITCase.java @@ -0,0 +1,77 @@ +package sonia.scm.it; + +import org.apache.http.HttpStatus; +import org.junit.Before; +import org.junit.Test; +import sonia.scm.it.utils.ScmRequests; +import sonia.scm.it.utils.TestData; +import sonia.scm.web.VndMediaType; + +import static org.junit.Assert.assertNotNull; +import static sonia.scm.it.PermissionsITCase.USER_PASS; +import static sonia.scm.it.utils.RestUtil.ADMIN_PASSWORD; +import static sonia.scm.it.utils.RestUtil.ADMIN_USERNAME; +import static sonia.scm.it.utils.RestUtil.given; +import static sonia.scm.it.utils.TestData.USER_SCM_ADMIN; +import static sonia.scm.it.utils.TestData.callRepository; + +public class RoleITCase { + + private static final String USER = "user"; + public static final String ROLE_NAME = "permission-role"; + + @Before + public void init() { + TestData.createDefault(); + TestData.createNotAdminUser(USER, USER_PASS); + } + + @Test + public void userShouldSeePermissionsAfterAddingRoleToUser() { + callRepository(USER, USER_PASS, "git", HttpStatus.SC_FORBIDDEN); + + String repositoryRolesUrl = new ScmRequests() + .requestIndexResource(ADMIN_USERNAME, ADMIN_PASSWORD) + .getUrl("repositoryRoles"); + + given() + .when() + .delete(repositoryRolesUrl + ROLE_NAME) + .then() + .statusCode(HttpStatus.SC_NO_CONTENT); + + given(VndMediaType.REPOSITORY_ROLE) + .when() + .content("{" + + "\"name\": \"" + ROLE_NAME + "\"," + + "\"verbs\": [\"read\",\"permissionRead\"]" + + "}") + .post(repositoryRolesUrl) + .then() + .statusCode(HttpStatus.SC_CREATED); + + String permissionUrl = given(VndMediaType.REPOSITORY, USER_SCM_ADMIN, USER_SCM_ADMIN) + .when() + .get(TestData.getDefaultRepositoryUrl("git")) + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .body().jsonPath().getString("_links.permissions.href"); + + given(VndMediaType.REPOSITORY_PERMISSION) + .when() + .content("{\n" + + "\t\"role\": \"" + ROLE_NAME + "\",\n" + + "\t\"name\": \"" + USER + "\",\n" + + "\t\"groupPermission\": false\n" + + "\t\n" + + "}") + .post(permissionUrl) + .then() + .statusCode(HttpStatus.SC_CREATED); + + assertNotNull(callRepository(USER, USER_PASS, "git", HttpStatus.SC_OK) + .extract() + .body().jsonPath().getString("_links.permissions.href")); + } +} diff --git a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java index 2164617772..69b9940f70 100644 --- a/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java +++ b/scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java @@ -201,7 +201,12 @@ public class ScmRequests { return super.assertPropertyPathDoesNotExists(LINK_USERS); } - + public String getUrl(String linkName) { + return response + .then() + .extract() + .path("_links." + linkName + ".href"); + } } public class RepositoryResponse extends ModelResponse, PREV> { diff --git a/scm-it/src/test/java/sonia/scm/it/utils/TestData.java b/scm-it/src/test/java/sonia/scm/it/utils/TestData.java index 23228d686b..20de47ffa4 100644 --- a/scm-it/src/test/java/sonia/scm/it/utils/TestData.java +++ b/scm-it/src/test/java/sonia/scm/it/utils/TestData.java @@ -106,14 +106,31 @@ public class TestData { ; } - public static void createUserPermission(String name, Collection permissionType, String repositoryType) { + public static void createUserPermission(String username, Collection verbs, String repositoryType) { String defaultPermissionUrl = TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType); - LOG.info("create permission with name {} and type: {} using the endpoint: {}", name, permissionType, defaultPermissionUrl); + LOG.info("create permission with name {} and verbs {} using the endpoint: {}", username, verbs, defaultPermissionUrl); given(VndMediaType.REPOSITORY_PERMISSION) .when() .content("{\n" + - "\t\"verbs\": " + permissionType.stream().collect(Collectors.joining("\",\"", "[\"", "\"]")) + ",\n" + - "\t\"name\": \"" + name + "\",\n" + + "\t\"verbs\": " + verbs.stream().collect(Collectors.joining("\",\"", "[\"", "\"]")) + ",\n" + + "\t\"name\": \"" + username + "\",\n" + + "\t\"groupPermission\": false\n" + + "\t\n" + + "}") + .post(defaultPermissionUrl) + .then() + .statusCode(HttpStatus.SC_CREATED) + ; + } + + public static void createUserPermission(String username, String roleName, String repositoryType) { + String defaultPermissionUrl = TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType); + LOG.info("create permission with name {} and role {} using the endpoint: {}", username, roleName, defaultPermissionUrl); + given(VndMediaType.REPOSITORY_PERMISSION) + .when() + .content("{\n" + + "\t\"role\": " + roleName + ",\n" + + "\t\"name\": \"" + username + "\",\n" + "\t\"groupPermission\": false\n" + "\t\n" + "}") diff --git a/scm-ui-components/packages/ui-components/src/buttons/Button.js b/scm-ui-components/packages/ui-components/src/buttons/Button.js index 2102bb540a..5e80db1e45 100644 --- a/scm-ui-components/packages/ui-components/src/buttons/Button.js +++ b/scm-ui-components/packages/ui-components/src/buttons/Button.js @@ -12,6 +12,8 @@ export type ButtonProps = { fullWidth?: boolean, className?: string, children?: React.Node, + + // context props classes: any }; diff --git a/scm-ui-components/packages/ui-types/src/AvailableRepositoryPermissions.js b/scm-ui-components/packages/ui-types/src/AvailableRepositoryPermissions.js deleted file mode 100644 index ab7e8d82e4..0000000000 --- a/scm-ui-components/packages/ui-types/src/AvailableRepositoryPermissions.js +++ /dev/null @@ -1,11 +0,0 @@ -// @flow - -export type RepositoryRole = { - name: string, - verbs: string[] -}; - -export type AvailableRepositoryPermissions = { - availableVerbs: string[], - availableRoles: RepositoryRole[] -}; diff --git a/scm-ui-components/packages/ui-types/src/RepositoryPermissions.js b/scm-ui-components/packages/ui-types/src/RepositoryPermissions.js index ed3c925283..14a2298fbe 100644 --- a/scm-ui-components/packages/ui-types/src/RepositoryPermissions.js +++ b/scm-ui-components/packages/ui-types/src/RepositoryPermissions.js @@ -1,14 +1,15 @@ //@flow import type {Links} from "./hal"; +export type PermissionCreateEntry = { + name: string, + role?: string, + verbs?: string[], + groupPermission: boolean +} + export type Permission = PermissionCreateEntry & { _links: Links }; -export type PermissionCreateEntry = { - name: string, - verbs: string[], - groupPermission: boolean -} - export type PermissionCollection = Permission[]; diff --git a/scm-ui-components/packages/ui-types/src/RepositoryRole.js b/scm-ui-components/packages/ui-types/src/RepositoryRole.js new file mode 100644 index 0000000000..195bdfe05c --- /dev/null +++ b/scm-ui-components/packages/ui-types/src/RepositoryRole.js @@ -0,0 +1,13 @@ +// @flow + +import type {Links} from "./hal"; + +export type RepositoryRole = { + name: string, + verbs: string[], + type?: string, + creationDate?: string, + lastModified?: string, + _links: Links +}; + diff --git a/scm-ui-components/packages/ui-types/src/index.js b/scm-ui-components/packages/ui-types/src/index.js index c57a3c6792..4024710300 100644 --- a/scm-ui-components/packages/ui-types/src/index.js +++ b/scm-ui-components/packages/ui-types/src/index.js @@ -25,6 +25,6 @@ export type { SubRepository, File } from "./Sources"; export type { SelectValue, AutocompleteObject } from "./Autocomplete"; -export type { AvailableRepositoryPermissions, RepositoryRole } from "./AvailableRepositoryPermissions"; +export type { RepositoryRole } from "./RepositoryRole"; export type { NamespaceStrategies } from "./NamespaceStrategies"; diff --git a/scm-ui/public/locales/de/config.json b/scm-ui/public/locales/de/config.json index b255e1c233..d26552c30d 100644 --- a/scm-ui/public/locales/de/config.json +++ b/scm-ui/public/locales/de/config.json @@ -6,6 +6,43 @@ "errorTitle": "Fehler", "errorSubtitle": "Unbekannter Einstellungen Fehler" }, + "repositoryRole": { + "navLink": "Berechtigungsrollen", + "title": "Berechtigungsrollen", + "noPermissionRoles": "Keine Berechtigungsrollen gefunden.", + "system": "System", + "createButton": "Berechtigungsrolle erstellen", + "name": "Name", + "type": "Typ", + "verbs": "Berechtigungen", + "button": { + "edit": "Bearbeiten" + }, + "create": { + "name": "Name" + }, + "edit": "Berechtigungsrolle bearbeiten", + "form": { + "subtitle": "Berechtigungsrolle bearbeiten", + "name": "Name", + "permissions": "Berechtigungen", + "submit": "Speichern" + } + }, + "role": { + "name": "Name", + "system": "System" + }, + "deleteRole" : { + "button": "Löschen", + "subtitle": "Berechtigungsrolle löschen", + "confirmAlert": { + "title": "Berechtigungsrolle löschen", + "message": "Soll die Berechtigungsrolle wirklich gelöscht werden? Alle Nutzer dieser Rolle verlieren Ihre Berechtigungen.", + "submit": "Ja", + "cancel": "Nein" + } + }, "config-form": { "submit": "Speichern", "submit-success-notification": "Einstellungen wurden erfolgreich geändert!", diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index f4ee071613..4ba7a725e6 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -119,6 +119,7 @@ "error-subtitle": "Unbekannter Fehler bei Berechtigung", "name": "Benutzer oder Gruppe", "role": "Rolle", + "custom": "CUSTOM", "permissions": "Berechtigung", "group-permission": "Gruppenberechtigung", "user-permission": "Benutzerberechtigung", diff --git a/scm-ui/public/locales/en/config.json b/scm-ui/public/locales/en/config.json index aeb62c1847..b2f6e77e0d 100644 --- a/scm-ui/public/locales/en/config.json +++ b/scm-ui/public/locales/en/config.json @@ -6,6 +6,43 @@ "errorTitle": "Error", "errorSubtitle": "Unknown Config Error" }, + "repositoryRole": { + "navLink": "Permission Roles", + "title": "Permission Roles", + "noPermissionRoles": "No permission roles found.", + "system": "System", + "createButton": "Create Permission Role", + "name": "Name", + "type": "Type", + "verbs": "Permissions", + "edit": "Edit Permission Role", + "button": { + "edit": "Edit" + }, + "create": { + "name": "Name" + }, + "form": { + "subtitle": "Edit Permission Role", + "name": "Name", + "permissions": "Permissions", + "submit": "Save" + } + }, + "role": { + "name": "Name", + "system": "System" + }, + "deleteRole" : { + "button": "Delete", + "subtitle": "Delete Permission Role", + "confirmAlert": { + "title": "Delete Permission Role", + "message": "Do you really want to delete this permission role? All users who own this role will lose their permissions.", + "submit": "Yes", + "cancel": "No" + } + }, "config-form": { "submit": "Submit", "submit-success-notification": "Configuration changed successfully!", diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 7344547d49..9a2e83f983 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -122,6 +122,7 @@ "error-subtitle": "Unknown permissions error", "name": "User or group", "role": "Role", + "custom": "CUSTOM", "permissions": "Permissions", "group-permission": "Group Permission", "user-permission": "User Permission", diff --git a/scm-ui/src/config/containers/Config.js b/scm-ui/src/config/containers/Config.js index 04de525c95..496dce4611 100644 --- a/scm-ui/src/config/containers/Config.js +++ b/scm-ui/src/config/containers/Config.js @@ -1,16 +1,18 @@ // @flow import React from "react"; import { translate } from "react-i18next"; -import { Route } from "react-router"; +import { Route, Switch } from "react-router-dom"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; - -import type { Links } from "@scm-manager/ui-types"; -import { Page, Navigation, NavLink, Section } from "@scm-manager/ui-components"; -import GlobalConfig from "./GlobalConfig"; import type { History } from "history"; import { connect } from "react-redux"; import { compose } from "redux"; +import type { Links } from "@scm-manager/ui-types"; +import { Page, Navigation, NavLink, Section } from "@scm-manager/ui-components"; import { getLinks } from "../../modules/indexResource"; +import GlobalConfig from "./GlobalConfig"; +import RepositoryRoles from "../roles/containers/RepositoryRoles"; +import SingleRepositoryRole from "../roles/containers/SingleRepositoryRole"; +import CreateRepositoryRole from "../roles/containers/CreateRepositoryRole"; type Props = { links: Links, @@ -33,6 +35,12 @@ class Config extends React.Component { return this.stripEndingSlash(this.props.match.url); }; + matchesRoles = (route: any) => { + const url = this.matchedUrl(); + const regex = new RegExp(`${url}/role/`); + return route.location.pathname.match(regex); + }; + render() { const { links, t } = this.props; @@ -46,12 +54,44 @@ class Config extends React.Component {
- - + + + ( + + )} + /> + } + /> + ( + + )} + /> + ( + + )} + /> + +
@@ -60,6 +100,11 @@ class Config extends React.Component { to={`${url}`} label={t("config.globalConfigurationNavLink")} /> + string +}; + +const styles = { + spacing: { + padding: "0 !important" + } +}; + +class AvailableVerbs extends React.Component { + render() { + const { role, t, classes } = this.props; + + let verbs = null; + if (role.verbs.length > 0) { + verbs = ( + + +
    + {role.verbs.map(verb => { + return ( +
  • {t("verbs.repository." + verb + ".displayName")}
  • + ); + })} +
+ + + ); + } + return verbs; + } +} + +export default compose( + injectSheet(styles), + translate("plugins") +)(AvailableVerbs); diff --git a/scm-ui/src/config/roles/components/PermissionRoleDetails.js b/scm-ui/src/config/roles/components/PermissionRoleDetails.js new file mode 100644 index 0000000000..1977ddde2a --- /dev/null +++ b/scm-ui/src/config/roles/components/PermissionRoleDetails.js @@ -0,0 +1,52 @@ +//@flow +import React from "react"; +import { translate } from "react-i18next"; +import type { RepositoryRole } from "@scm-manager/ui-types"; +import ExtensionPoint from "@scm-manager/ui-extensions/lib/ExtensionPoint"; +import PermissionRoleDetailsTable from "./PermissionRoleDetailsTable"; +import { Button, Subtitle } from "@scm-manager/ui-components"; + +type Props = { + role: RepositoryRole, + url: string, + + // context props + t: string => string +}; + +class PermissionRoleDetails extends React.Component { + renderEditButton() { + const { t, url } = this.props; + if (!!this.props.role._links.update) { + return ( +