diff --git a/gradle/changelog/cli_repository_permission_commands.yaml b/gradle/changelog/cli_repository_permission_commands.yaml index 52a431b5be..7bab5a5772 100644 --- a/gradle/changelog/cli_repository_permission_commands.yaml +++ b/gradle/changelog/cli_repository_permission_commands.yaml @@ -1,2 +1,2 @@ - type: added - description: Cli commands to modify repository permissions ([#2090](https://github.com/scm-manager/scm-manager/pull/2090)) + description: Cli commands to modify repository and namespace permissions ([#2090](https://github.com/scm-manager/scm-manager/pull/2090) and [#2093](https://github.com/scm-manager/scm-manager/pull/2093)) diff --git a/scm-core/src/main/java/sonia/scm/repository/Namespace.java b/scm-core/src/main/java/sonia/scm/repository/Namespace.java index 8ad56c3b90..bfdad435b7 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Namespace.java +++ b/scm-core/src/main/java/sonia/scm/repository/Namespace.java @@ -46,7 +46,7 @@ import static java.util.Collections.unmodifiableCollection; ) @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "namespaces") -public class Namespace implements PermissionObject, Cloneable { +public class Namespace implements PermissionObject, Cloneable, RepositoryPermissionHolder { private String namespace; private Set permissions = new HashSet<>(); 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 958e26cde5..5af2086488 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -46,7 +46,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; /** @@ -65,7 +64,9 @@ import java.util.Set; @Guard(guard = RepositoryPermissionGuard.class) } ) -public class Repository extends BasicPropertiesAware implements ModelObject, PermissionObject, RepositoryCoordinates { +public class Repository + extends BasicPropertiesAware + implements ModelObject, PermissionObject, RepositoryCoordinates, RepositoryPermissionHolder { private static final long serialVersionUID = 3486560714961909711L; @@ -210,28 +211,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per return Collections.unmodifiableCollection(permissions); } - /** - * Returns the permission for the given user, if present, or an empty {@link Optional} otherwise. - * - * @since 2.38.0 - */ - public Optional findUserPermission(String userId) { - return findPermission(userId, false); - } - - /** - * Returns the permission for the given group, if present, or an empty {@link Optional} otherwise. - * - * @since 2.38.0 - */ - public Optional findGroupPermission(String groupId) { - return findPermission(groupId, true); - } - - private Optional findPermission(String x, boolean isGroup) { - return getPermissions().stream().filter(p -> p.isGroupPermission() == isGroup && p.getName().equals(x)).findFirst(); - } - /** * Returns the type (hg, git, svn ...) of the {@link Repository}. * diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermissionHolder.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermissionHolder.java new file mode 100644 index 0000000000..4fcb1dfae1 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermissionHolder.java @@ -0,0 +1,84 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import java.util.Collection; +import java.util.Optional; + +/** + * This abstracts the permissions of {@link Repository} and {@link Namespace} objects. + */ +public interface RepositoryPermissionHolder { + + /** + * Returns a collection of all permissions for this object. + */ + Collection getPermissions(); + + /** + * Sets and therefore overwrites the permissions for this object. + * + * @param permissions The new permissions for this object. + */ + void setPermissions(Collection permissions); + + /** + * Adds a single permission to the current set of permissions for this object. + * + * @param newPermission The new permission that will be added to the existing permissions. + */ + void addPermission(RepositoryPermission newPermission); + + /** + * Removes a single permission from the current set of permissions for this object. + * + * @param permission The permission that should be removed from the existing permissions. + * @return true, if the given permission was part of the permissions for this object, false + * otherwise. + */ + boolean removePermission(RepositoryPermission permission); + + /** + * Returns the permission for the given user, if present, or an empty {@link Optional} otherwise. + * + * @since 2.38.0 + */ + default Optional findUserPermission(String userId) { + return findPermission(userId, false); + } + + /** + * Returns the permission for the given group, if present, or an empty {@link Optional} otherwise. + * + * @since 2.38.0 + */ + default Optional findGroupPermission(String groupId) { + return findPermission(groupId, true); + } + + private Optional findPermission(String name, boolean isGroup) { + return getPermissions().stream().filter(p -> p.isGroupPermission() == isGroup && p.getName().equals(name)).findFirst(); + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/cli/NamespaceCommand.java b/scm-core/src/main/java/sonia/scm/repository/cli/NamespaceCommand.java new file mode 100644 index 0000000000..98e7a6491b --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/cli/NamespaceCommand.java @@ -0,0 +1,30 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import picocli.CommandLine; + +@CommandLine.Command(name = "namespace") +public class NamespaceCommand {} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespaceListCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespaceListCommand.java new file mode 100644 index 0000000000..7068eba2b0 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespaceListCommand.java @@ -0,0 +1,60 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.cli.TemplateRenderer; +import sonia.scm.repository.RepositoryManager; + +import javax.inject.Inject; + +import static java.util.Collections.emptyMap; + +@ParentCommand(value = NamespaceCommand.class) +@CommandLine.Command(name = "list", aliases = "ls") +class NamespaceListCommand implements Runnable { + + + @CommandLine.Mixin + private final TemplateRenderer templateRenderer; + private final RepositoryManager manager; + + @Inject + public NamespaceListCommand(RepositoryManager manager, TemplateRenderer templateRenderer, RepositoryToRepositoryCommandDtoMapper mapper) { + this.manager = manager; + this.templateRenderer = templateRenderer; + } + + @Override + public void run() { + manager.getAllNamespaces() + .forEach(this::render); + } + + private void render(String namespace) { + templateRenderer.renderToStdout(namespace + "\n", emptyMap()); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionBaseAdapter.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionBaseAdapter.java new file mode 100644 index 0000000000..14a8c6098d --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionBaseAdapter.java @@ -0,0 +1,59 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import sonia.scm.repository.Namespace; +import sonia.scm.repository.NamespaceManager; + +import java.util.Optional; + +import static java.util.Optional.empty; + +class NamespacePermissionBaseAdapter implements PermissionBaseAdapter { + + private final NamespaceManager namespaceManager; + private final RepositoryTemplateRenderer templateRenderer; + + NamespacePermissionBaseAdapter(NamespaceManager namespaceManager, RepositoryTemplateRenderer templateRenderer) { + this.namespaceManager = namespaceManager; + this.templateRenderer = templateRenderer; + } + + @Override + public Optional get(String namespace) { + Optional ns = namespaceManager.get(namespace); + if (ns.isPresent()) { + return ns; + } else { + templateRenderer.renderNotFoundError(); + return empty(); + } + } + + @Override + public void modify(Namespace namespace) { + namespaceManager.modify(namespace); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsAddCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsAddCommand.java new file mode 100644 index 0000000000..f2288eae5e --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsAddCommand.java @@ -0,0 +1,58 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.cli.PermissionDescriptionResolver; +import sonia.scm.repository.Namespace; +import sonia.scm.repository.NamespaceManager; +import sonia.scm.repository.RepositoryRoleManager; + +import javax.inject.Inject; + +@CommandLine.Command(name = "add-permissions") +@ParentCommand(value = NamespaceCommand.class) +class NamespacePermissionsAddCommand extends PermissionsAddCommand implements Runnable { + + @CommandLine.Parameters(paramLabel = "namespace", index = "0", descriptionKey = "scm.namespace.add-permissions.namespace") + private String namespace; + + @Inject + NamespacePermissionsAddCommand(NamespaceManager namespaceManager, RepositoryRoleManager roleManager, PermissionDescriptionResolver permissionDescriptionResolver, RepositoryTemplateRenderer templateRenderer) { + super(roleManager, permissionDescriptionResolver, templateRenderer, new NamespacePermissionBaseAdapter(namespaceManager, templateRenderer)); + } + + @Override + String getIdentifier() { + return namespace; + } + + @VisibleForTesting + void setNamespace(String namespace) { + this.namespace = namespace; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsAvailableCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsAvailableCommand.java new file mode 100644 index 0000000000..ac98919a8d --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsAvailableCommand.java @@ -0,0 +1,43 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.cli.PermissionDescriptionResolver; +import sonia.scm.repository.RepositoryRoleManager; +import sonia.scm.security.RepositoryPermissionProvider; + +import javax.inject.Inject; + +@CommandLine.Command(name = "available-permissions") +@ParentCommand(value = NamespaceCommand.class) +class NamespacePermissionsAvailableCommand extends PermissionsAvailableCommand { + + @Inject + public NamespacePermissionsAvailableCommand(RepositoryTemplateRenderer templateRenderer, RepositoryPermissionProvider repositoryPermissionProvider, PermissionDescriptionResolver permissionDescriptionResolver, RepositoryRoleManager repositoryRoleManager) { + super(templateRenderer, repositoryPermissionProvider, permissionDescriptionResolver, repositoryRoleManager); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsClearCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsClearCommand.java new file mode 100644 index 0000000000..87ad615971 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsClearCommand.java @@ -0,0 +1,51 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.repository.Namespace; +import sonia.scm.repository.NamespaceManager; +import sonia.scm.repository.RepositoryRoleManager; + +import javax.inject.Inject; + +@CommandLine.Command(name = "clear-permissions") +@ParentCommand(value = NamespaceCommand.class) +class NamespacePermissionsClearCommand extends PermissionClearCommand { + + @CommandLine.Parameters(paramLabel = "namespace", index = "0", descriptionKey = "scm.namespace.clear-permissions.namespace") + private String namespace; + + @Inject + public NamespacePermissionsClearCommand(NamespaceManager namespaceManager, RepositoryRoleManager roleManager, RepositoryTemplateRenderer templateRenderer) { + super(roleManager, templateRenderer, new NamespacePermissionBaseAdapter(namespaceManager, templateRenderer)); + } + + @Override + String getIdentifier() { + return namespace; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsListCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsListCommand.java new file mode 100644 index 0000000000..4859ac47a7 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsListCommand.java @@ -0,0 +1,57 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.CommandValidator; +import sonia.scm.cli.ParentCommand; +import sonia.scm.repository.Namespace; +import sonia.scm.repository.NamespaceManager; + +import javax.inject.Inject; + +@CommandLine.Command(name = "list-permissions") +@ParentCommand(value = NamespaceCommand.class) +class NamespacePermissionsListCommand extends PermissionsListCommand { + + @CommandLine.Parameters(paramLabel = "namespace", index = "0", descriptionKey = "scm.namespace.list-permissions.namespace") + private String namespace; + + @Inject + public NamespacePermissionsListCommand(RepositoryTemplateRenderer templateRenderer, CommandValidator validator, NamespaceManager manager, RepositoryPermissionBeanMapper beanMapper) { + super(templateRenderer, validator, new NamespacePermissionBaseAdapter(manager, templateRenderer), beanMapper); + } + + @Override + String getIdentifier() { + return namespace; + } + + @VisibleForTesting + void setNamespace(String namespace) { + this.namespace = namespace; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsRemoveCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsRemoveCommand.java new file mode 100644 index 0000000000..aefbfe2c36 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsRemoveCommand.java @@ -0,0 +1,56 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.repository.Namespace; +import sonia.scm.repository.NamespaceManager; +import sonia.scm.repository.RepositoryRoleManager; + +import javax.inject.Inject; + +@CommandLine.Command(name = "remove-permissions") +@ParentCommand(value = NamespaceCommand.class) +class NamespacePermissionsRemoveCommand extends PermissionsRemoveCommand { + + @CommandLine.Parameters(paramLabel = "namespace", index = "0", descriptionKey = "scm.namespace.remove-permissions.namespace") + private String namespace; + @Inject + public NamespacePermissionsRemoveCommand(NamespaceManager namespaceManager, RepositoryRoleManager roleManager, RepositoryTemplateRenderer templateRenderer) { + super(roleManager, templateRenderer, new NamespacePermissionBaseAdapter(namespaceManager, templateRenderer)); + } + + @Override + String getIdentifier() { + return namespace; + } + + @VisibleForTesting + void setNamespace(String namespace) { + this.namespace = namespace; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsSetRoleCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsSetRoleCommand.java new file mode 100644 index 0000000000..42ba1e80d3 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/NamespacePermissionsSetRoleCommand.java @@ -0,0 +1,57 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.repository.Namespace; +import sonia.scm.repository.NamespaceManager; +import sonia.scm.repository.RepositoryRoleManager; + +import javax.inject.Inject; + +@CommandLine.Command(name = "set-role") +@ParentCommand(value = NamespaceCommand.class) +class NamespacePermissionsSetRoleCommand extends PermissionsSetRoleCommand { + + @CommandLine.Parameters(paramLabel = "namespace", index = "0", descriptionKey = "scm.namespace.set-role.namespace") + private String namespace; + + @Inject + public NamespacePermissionsSetRoleCommand(NamespaceManager namespaceManager, RepositoryRoleManager roleManager, RepositoryTemplateRenderer templateRenderer) { + super(roleManager, templateRenderer, new NamespacePermissionBaseAdapter(namespaceManager, templateRenderer)); + } + + @Override + protected String getIdentifier() { + return namespace; + } + + @VisibleForTesting + void setNamespace(String namespace) { + this.namespace = namespace; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionBaseAdapter.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionBaseAdapter.java new file mode 100644 index 0000000000..e689eed410 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionBaseAdapter.java @@ -0,0 +1,36 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import sonia.scm.repository.RepositoryPermissionHolder; + +import java.util.Optional; + +public interface PermissionBaseAdapter { + + Optional get(String identifier); + + void modify(T object); +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionBaseCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionBaseCommand.java similarity index 55% rename from scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionBaseCommand.java rename to scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionBaseCommand.java index 3ec5365261..05532d5fcf 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionBaseCommand.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionBaseCommand.java @@ -25,10 +25,8 @@ package sonia.scm.repository.cli; import picocli.CommandLine; -import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryPermission; +import sonia.scm.repository.RepositoryPermissionHolder; import sonia.scm.repository.RepositoryRoleManager; import javax.inject.Inject; @@ -37,64 +35,60 @@ import java.util.HashSet; import java.util.Optional; import java.util.function.Predicate; -class RepositoryPermissionBaseCommand { +abstract class PermissionBaseCommand { - private final RepositoryManager repositoryManager; private final RepositoryRoleManager roleManager; @CommandLine.Mixin private final RepositoryTemplateRenderer templateRenderer; + private final PermissionBaseAdapter adapter; @Inject - RepositoryPermissionBaseCommand(RepositoryManager repositoryManager, RepositoryRoleManager roleManager, RepositoryTemplateRenderer templateRenderer) { - this.repositoryManager = repositoryManager; + PermissionBaseCommand(RepositoryRoleManager roleManager, RepositoryTemplateRenderer templateRenderer, PermissionBaseAdapter adapter) { this.roleManager = roleManager; this.templateRenderer = templateRenderer; + this.adapter = adapter; } - void modifyRepository(String repositoryName, Predicate modifier) { - NamespaceAndName namespaceAndName; - try { - namespaceAndName = NamespaceAndName.fromString(repositoryName); - } catch (IllegalArgumentException e) { - templateRenderer.renderInvalidInputError(); - return; - } + Optional get(String identifier) { + return adapter.get(identifier); + } - Repository repository = repositoryManager.get(namespaceAndName); - if (repository != null) { - if (modifier.test(repository)) { - repositoryManager.modify(repository); - } - } else { - templateRenderer.renderNotFoundError(); + void set(T object) { + adapter.modify(object); + } + + void modify(String identifier, Predicate modifier) { + Optional ns = get(identifier); + if (ns.isPresent() && modifier.test(ns.get())) { + set(ns.get()); } } - void replacePermission(Repository repository, RepositoryPermission permission) { - this.removeExistingPermission(repository, permission.getName(), permission.isGroupPermission()); - repository.addPermission(permission); + void replacePermission(T namespace, RepositoryPermission permission) { + removeExistingPermission(namespace, permission.getName(), permission.isGroupPermission()); + namespace.addPermission(permission); } - void removeExistingPermission(Repository repository, String name, boolean forGroup) { + void removeExistingPermission(T permissionHolder, String name, boolean forGroup) { if (!forGroup) { - repository.findUserPermission(name).ifPresent(repository::removePermission); + permissionHolder.findUserPermission(name).ifPresent(permissionHolder::removePermission); } else { - repository.findGroupPermission(name).ifPresent(repository::removePermission); + permissionHolder.findGroupPermission(name).ifPresent(permissionHolder::removePermission); } } - HashSet getPermissionsAsModifiableSet(Repository repository, String name, boolean forGroup) { - return this.getExistingPermissions(repository, name, forGroup) + HashSet getPermissionsAsModifiableSet(T permissionHolder, String name, boolean forGroup) { + return getExistingPermissions(permissionHolder, name, forGroup) .map(this::getVerbs) .map(HashSet::new) .orElseGet(HashSet::new); } - private Optional getExistingPermissions(Repository repo, String name, boolean forGroup) { + private Optional getExistingPermissions(T permissionHolder, String name, boolean forGroup) { if (!forGroup) { - return repo.findUserPermission(name); + return permissionHolder.findUserPermission(name); } else { - return repo.findGroupPermission(name); + return permissionHolder.findGroupPermission(name); } } @@ -110,7 +104,7 @@ class RepositoryPermissionBaseCommand { templateRenderer.renderRoleNotFoundError(); } - void renderVerbNotFoundError() { - templateRenderer.renderVerbNotFoundError(); + void renderVerbNotFoundError(String verb) { + templateRenderer.renderVerbNotFoundError(verb); } } diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionClearCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionClearCommand.java new file mode 100644 index 0000000000..8b053d4326 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionClearCommand.java @@ -0,0 +1,65 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.repository.RepositoryPermissionHolder; +import sonia.scm.repository.RepositoryRoleManager; + +abstract class PermissionClearCommand extends PermissionBaseCommand implements Runnable { + + @CommandLine.Parameters(paramLabel = "name", index = "1", descriptionKey = "scm.repo.clear-permissions.name") + private String name; + @CommandLine.Option(names = {"--group", "-g"}, descriptionKey = "scm.repo.clear-permissions.forGroup") + private boolean forGroup; + + PermissionClearCommand(RepositoryRoleManager roleManager, RepositoryTemplateRenderer templateRenderer, PermissionBaseAdapter adapter) { + super(roleManager, templateRenderer, adapter); + } + + @Override + public void run() { + modify( + getIdentifier(), + ns -> { + removeExistingPermission(ns, name, forGroup); + return true; + } + ); + } + + abstract String getIdentifier(); + + @VisibleForTesting + void setName(String name) { + this.name = name; + } + + @VisibleForTesting + void setForGroup(boolean forGroup) { + this.forGroup = forGroup; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionsAddCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionsAddCommand.java new file mode 100644 index 0000000000..45e057b7a8 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionsAddCommand.java @@ -0,0 +1,99 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.PermissionDescriptionResolver; +import sonia.scm.repository.RepositoryPermission; +import sonia.scm.repository.RepositoryPermissionHolder; +import sonia.scm.repository.RepositoryRoleManager; + +import java.util.Arrays; +import java.util.Set; + +import static java.util.Arrays.asList; + +abstract class PermissionsAddCommand extends PermissionBaseCommand implements Runnable { + + private final PermissionDescriptionResolver permissionDescriptionResolver; + + @CommandLine.Parameters(paramLabel = "name", index = "1", descriptionKey = "scm.repo.add-permissions.name") + private String name; + @CommandLine.Parameters(paramLabel = "verbs", index = "2..", arity = "1..", descriptionKey = "scm.repo.add-permissions.verbs") + private String[] verbs = new String[0]; + @CommandLine.Option(names = {"--group", "-g"}, descriptionKey = "scm.repo.add-permissions.forGroup") + private boolean forGroup; + + PermissionsAddCommand(RepositoryRoleManager roleManager, PermissionDescriptionResolver permissionDescriptionResolver, RepositoryTemplateRenderer templateRenderer, PermissionBaseAdapter adapter) { + super(roleManager, templateRenderer, adapter); + this.permissionDescriptionResolver = permissionDescriptionResolver; + } + + abstract String getIdentifier(); + + @Override + public void run() { + modify( + getIdentifier(), + ns -> { + if (!Arrays.stream(verbs).allMatch(this::verifyVerbExists)) { + return false; + } + Set resultingVerbs = + getPermissionsAsModifiableSet(ns, name, forGroup); + if (resultingVerbs.containsAll(asList(this.verbs))) { + return false; + } + resultingVerbs.addAll(asList(this.verbs)); + replacePermission(ns, new RepositoryPermission(name, resultingVerbs, forGroup)); + return true; + } + ); + } + + private boolean verifyVerbExists(String verb) { + if (permissionDescriptionResolver.getDescription(verb).isEmpty()) { + renderVerbNotFoundError(verb); + return false; + } + return true; + } + + @VisibleForTesting + void setName(String name) { + this.name = name; + } + + @VisibleForTesting + void setVerbs(String... verbs) { + this.verbs = verbs; + } + + @VisibleForTesting + void setForGroup(boolean forGroup) { + this.forGroup = forGroup; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionsAvailableCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionsAvailableCommand.java new file mode 100644 index 0000000000..35f07a9bef --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionsAvailableCommand.java @@ -0,0 +1,94 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.PermissionDescriptionResolver; +import sonia.scm.repository.RepositoryRole; +import sonia.scm.repository.RepositoryRoleManager; +import sonia.scm.security.RepositoryPermissionProvider; + +import java.util.List; + +import static java.util.stream.Collectors.toList; + +class PermissionsAvailableCommand implements Runnable { + + @CommandLine.Option(names = {"--roles", "-r"}, descriptionKey = "scm.repo.available-permissions.roles-only") + private boolean roles; + @CommandLine.Option(names = {"--verbs", "-v"}, descriptionKey = "scm.repo.available-permissions.verbs-only") + private boolean verbs; + + @CommandLine.Mixin + private final RepositoryTemplateRenderer templateRenderer; + private final RepositoryPermissionProvider repositoryPermissionProvider; + private final PermissionDescriptionResolver permissionDescriptionResolver; + private final RepositoryRoleManager repositoryRoleManager; + + public PermissionsAvailableCommand(RepositoryTemplateRenderer templateRenderer, RepositoryPermissionProvider repositoryPermissionProvider, PermissionDescriptionResolver permissionDescriptionResolver, RepositoryRoleManager repositoryRoleManager) { + this.templateRenderer = templateRenderer; + this.repositoryPermissionProvider = repositoryPermissionProvider; + this.permissionDescriptionResolver = permissionDescriptionResolver; + this.repositoryRoleManager = repositoryRoleManager; + } + + @VisibleForTesting + void setRoles(boolean roles) { + this.roles = roles; + } + + @VisibleForTesting + void setVerbs(boolean verbs) { + this.verbs = verbs; + } + + @Override + public void run() { + if (roles) { + templateRenderer.renderRoles(getRoleBeans()); + } else if (verbs) { + templateRenderer.renderVerbs(getVerbBeans()); + } else { + templateRenderer.render(getRoleBeans(), getVerbBeans()); + } + } + + private List getVerbBeans() { + return repositoryPermissionProvider.availableVerbs().stream().map(this::createBean).collect(toList()); + } + + private List getRoleBeans() { + return repositoryRoleManager.getAll().stream().map(this::createBean).collect(toList()); + } + + private VerbBean createBean(String verb) { + return new VerbBean(verb, permissionDescriptionResolver.getDescription(verb).orElse(verb)); + } + + private RoleBean createBean(RepositoryRole role) { + return new RoleBean(role.getName(), role.getVerbs()); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionsListCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionsListCommand.java new file mode 100644 index 0000000000..526077c3c6 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionsListCommand.java @@ -0,0 +1,80 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.CommandValidator; +import sonia.scm.repository.RepositoryPermissionHolder; + +import java.util.Collection; +import java.util.Optional; + +abstract class PermissionsListCommand implements Runnable { + + @CommandLine.Mixin + private final RepositoryTemplateRenderer templateRenderer; + @CommandLine.Mixin + private final CommandValidator validator; + private final PermissionBaseAdapter adapter; + private final RepositoryPermissionBeanMapper beanMapper; + + @CommandLine.Option(names = {"--verbose", "-v"}, descriptionKey = "scm.repo.list-permissions.verbose") + private boolean verbose; + @CommandLine.Option(names = {"--keys", "-k"}, descriptionKey = "scm.repo.list-permissions.keys") + private boolean keys; + + PermissionsListCommand(RepositoryTemplateRenderer templateRenderer, CommandValidator validator, PermissionBaseAdapter adapter, RepositoryPermissionBeanMapper beanMapper) { + this.templateRenderer = templateRenderer; + this.validator = validator; + this.adapter = adapter; + this.beanMapper = beanMapper; + } + + @VisibleForTesting + void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + public void setKeys(boolean keys) { + this.keys = keys; + } + + @Override + public void run() { + validator.validate(); + Optional object = adapter.get(getIdentifier()); + if (object.isPresent()) { + Collection permissions = beanMapper.createPermissionBeans(object.get().getPermissions(), keys); + if (verbose) { + templateRenderer.renderVerbose(permissions); + } else { + templateRenderer.render(permissions); + } + } + } + + abstract String getIdentifier(); +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionsRemoveCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionsRemoveCommand.java new file mode 100644 index 0000000000..8052a20367 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionsRemoveCommand.java @@ -0,0 +1,84 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.repository.RepositoryPermission; +import sonia.scm.repository.RepositoryPermissionHolder; +import sonia.scm.repository.RepositoryRoleManager; + +import java.util.Set; + +import static java.util.Arrays.asList; + +abstract class PermissionsRemoveCommand extends PermissionBaseCommand implements Runnable { + + @CommandLine.Parameters(paramLabel = "name", index = "1", descriptionKey = "scm.repo.remove-permissions.name") + private String name; + @CommandLine.Parameters(paramLabel = "verbs", index = "2..", arity = "1..", descriptionKey = "scm.repo.remove-permissions.verbs") + private String[] verbs = new String[0]; + + @CommandLine.Option(names = {"--group", "-g"}, descriptionKey = "scm.repo.remove-permissions.forGroup") + private boolean forGroup; + + PermissionsRemoveCommand(RepositoryRoleManager roleManager, RepositoryTemplateRenderer templateRenderer, PermissionBaseAdapter adapter) { + super(roleManager, templateRenderer, adapter); + } + + @Override + public void run() { + modify( + getIdentifier(), + ns -> { + Set resultingVerbs = + getPermissionsAsModifiableSet(ns, name, forGroup); + if (resultingVerbs.stream().noneMatch(verb -> asList(verbs).contains(verb))) { + return false; + } + resultingVerbs.removeAll(asList(this.verbs)); + replacePermission(ns, new RepositoryPermission(name, resultingVerbs, forGroup)); + return true; + } + ); + } + + abstract String getIdentifier(); + + @VisibleForTesting + void setName(String name) { + this.name = name; + } + + @VisibleForTesting + void setVerbs(String... verbs) { + this.verbs = verbs; + } + + @VisibleForTesting + void setForGroup(boolean forGroup) { + this.forGroup = forGroup; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionsSetRoleCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionsSetRoleCommand.java new file mode 100644 index 0000000000..c0ad20de36 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/PermissionsSetRoleCommand.java @@ -0,0 +1,80 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.repository.RepositoryPermission; +import sonia.scm.repository.RepositoryPermissionHolder; +import sonia.scm.repository.RepositoryRoleManager; + +abstract class PermissionsSetRoleCommand extends PermissionBaseCommand implements Runnable { + + private final RepositoryRoleManager roleManager; + + @CommandLine.Parameters(paramLabel = "name", index = "1", descriptionKey = "scm.repo.set-role.name") + private String name; + @CommandLine.Parameters(paramLabel = "role", index = "2", descriptionKey = "scm.repo.set-role.role") + private String role; + @CommandLine.Option(names = {"--group", "-g"}, descriptionKey = "scm.repo.set-role.forGroup") + private boolean forGroup; + + PermissionsSetRoleCommand(RepositoryRoleManager roleManager, RepositoryTemplateRenderer templateRenderer, PermissionBaseAdapter adapter) { + super(roleManager, templateRenderer, adapter); + this.roleManager = roleManager; + } + + @Override + public void run() { + modify( + getIdentifier(), + ns -> { + if (roleManager.get(role) == null) { + renderRoleNotFoundError(); + return false; + } + replacePermission(ns, new RepositoryPermission(name, role, forGroup)); + return true; + } + ); + } + + protected abstract String getIdentifier(); + + @VisibleForTesting + void setRole(String role) { + this.role = role; + } + + @VisibleForTesting + void setName(String name) { + this.name = name; + } + + @VisibleForTesting + void setForGroup(boolean forGroup) { + this.forGroup = forGroup; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionBaseAdapter.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionBaseAdapter.java new file mode 100644 index 0000000000..63fbbc70eb --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionBaseAdapter.java @@ -0,0 +1,68 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryManager; + +import java.util.Optional; + +import static java.util.Optional.empty; +import static java.util.Optional.of; + +class RepositoryPermissionBaseAdapter implements PermissionBaseAdapter { + + private final RepositoryManager repositoryManager; + private final RepositoryTemplateRenderer templateRenderer; + + RepositoryPermissionBaseAdapter(RepositoryManager repositoryManager, RepositoryTemplateRenderer templateRenderer) { + this.repositoryManager = repositoryManager; + this.templateRenderer = templateRenderer; + } + + @Override + public Optional get(String repositoryNamespaceAndName) { + NamespaceAndName namespaceAndName; + try { + namespaceAndName = NamespaceAndName.fromString(repositoryNamespaceAndName); + } catch (IllegalArgumentException e) { + templateRenderer.renderInvalidInputError(); + return empty(); + } + Repository repository = repositoryManager.get(namespaceAndName); + if (repository == null) { + templateRenderer.renderNotFoundError(); + return empty(); + } else { + return of(repository); + } + } + + @Override + public void modify(Repository repository) { + repositoryManager.modify(repository); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionBeanMapper.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionBeanMapper.java new file mode 100644 index 0000000000..811f7a9437 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionBeanMapper.java @@ -0,0 +1,77 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import sonia.scm.cli.PermissionDescriptionResolver; +import sonia.scm.repository.RepositoryPermission; +import sonia.scm.repository.RepositoryRoleManager; + +import javax.inject.Inject; +import java.util.Collection; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.toList; + +class RepositoryPermissionBeanMapper { + + private final RepositoryRoleManager roleManager; + private final PermissionDescriptionResolver permissionDescriptionResolver; + + @Inject + RepositoryPermissionBeanMapper(RepositoryRoleManager roleManager, PermissionDescriptionResolver permissionDescriptionResolver) { + this.roleManager = roleManager; + this.permissionDescriptionResolver = permissionDescriptionResolver; + } + + Collection createPermissionBeans(Collection permissions, boolean keys) { + return permissions + .stream() + .map(permission -> createPermissionBean(permission, keys)) + .collect(toList()); + } + + private RepositoryPermissionBean createPermissionBean(RepositoryPermission permission, boolean keys) { + Collection effectiveVerbs; + if (permission.getRole() == null) { + effectiveVerbs = permission.getVerbs(); + } else { + effectiveVerbs = roleManager.get(permission.getRole()).getVerbs(); + } + return new RepositoryPermissionBean( + permission.isGroupPermission(), + permission.getName(), + permission.getRole() == null? "CUSTOM": permission.getRole(), + keys? effectiveVerbs: getDescriptions(effectiveVerbs) + ); + } + + private Collection getDescriptions(Collection effectiveVerbs) { + return effectiveVerbs.stream().map(this::getDescription).collect(Collectors.toList()); + } + + private String getDescription(String verb) { + return permissionDescriptionResolver.getDescription(verb).orElse(verb); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsAddCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsAddCommand.java index e78626071b..83a843e843 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsAddCommand.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsAddCommand.java @@ -28,82 +28,31 @@ import com.google.common.annotations.VisibleForTesting; import picocli.CommandLine; import sonia.scm.cli.ParentCommand; import sonia.scm.cli.PermissionDescriptionResolver; +import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; -import sonia.scm.repository.RepositoryPermission; import sonia.scm.repository.RepositoryRoleManager; import javax.inject.Inject; -import java.util.Arrays; -import java.util.Set; - -import static java.util.Arrays.asList; @CommandLine.Command(name = "add-permissions") @ParentCommand(value = RepositoryCommand.class) -class RepositoryPermissionsAddCommand extends RepositoryPermissionBaseCommand implements Runnable { - - private final PermissionDescriptionResolver permissionDescriptionResolver; +class RepositoryPermissionsAddCommand extends PermissionsAddCommand implements Runnable { @CommandLine.Parameters(paramLabel = "namespace/name", index = "0", descriptionKey = "scm.repo.add-permissions.repository") - private String repositoryName; - @CommandLine.Parameters(paramLabel = "name", index = "1", descriptionKey = "scm.repo.add-permissions.name") - private String name; - @CommandLine.Parameters(paramLabel = "verbs", index = "2..", arity = "1..", descriptionKey = "scm.repo.add-permissions.verbs") - private String[] verbs = new String[0]; - - @CommandLine.Option(names = {"--group", "-g"}, descriptionKey = "scm.repo.add-permissions.forGroup") - private boolean forGroup; + private String repositoryNamespaceAndName; @Inject - RepositoryPermissionsAddCommand(RepositoryManager repositoryManager, RepositoryPermissionBaseCommand permissionCommandManager, RepositoryRoleManager roleManager, PermissionDescriptionResolver permissionDescriptionResolver, RepositoryTemplateRenderer templateRenderer) { - super(repositoryManager, roleManager, templateRenderer); - this.permissionDescriptionResolver = permissionDescriptionResolver; + RepositoryPermissionsAddCommand(RepositoryManager repositoryManager, RepositoryRoleManager roleManager, PermissionDescriptionResolver permissionDescriptionResolver, RepositoryTemplateRenderer templateRenderer) { + super(roleManager, permissionDescriptionResolver, templateRenderer, new RepositoryPermissionBaseAdapter(repositoryManager, templateRenderer)); } @Override - public void run() { - modifyRepository( - repositoryName, - repository -> { - if (!Arrays.stream(verbs).allMatch(this::verifyVerbExists)) { - return false; - } - Set resultingVerbs = - getPermissionsAsModifiableSet(repository, name, forGroup); - if (resultingVerbs.containsAll(asList(this.verbs))) { - return false; - } - resultingVerbs.addAll(asList(this.verbs)); - replacePermission(repository, new RepositoryPermission(name, resultingVerbs, forGroup)); - return true; - } - ); - } - - private boolean verifyVerbExists(String verb) { - if (permissionDescriptionResolver.getDescription(verb).isEmpty()) { - renderVerbNotFoundError(); - return false; - } - return true; + String getIdentifier() { + return repositoryNamespaceAndName; } @VisibleForTesting - void setRepositoryName(String repositoryName) { - this.repositoryName = repositoryName; - } - - @VisibleForTesting - void setName(String name) { - this.name = name; - } - - @VisibleForTesting - void setVerbs(String... verbs) { - this.verbs = verbs; - } - - public void setForGroup(boolean forGroup) { - this.forGroup = forGroup; + void setRepositoryNamespaceAndName(String repositoryNamespaceAndName) { + this.repositoryNamespaceAndName = repositoryNamespaceAndName; } } diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsAvailableCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsAvailableCommand.java index a36d18ae0b..d7250cf2f3 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsAvailableCommand.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsAvailableCommand.java @@ -24,76 +24,20 @@ package sonia.scm.repository.cli; -import com.google.common.annotations.VisibleForTesting; import picocli.CommandLine; import sonia.scm.cli.ParentCommand; import sonia.scm.cli.PermissionDescriptionResolver; -import sonia.scm.repository.RepositoryRole; import sonia.scm.repository.RepositoryRoleManager; import sonia.scm.security.RepositoryPermissionProvider; import javax.inject.Inject; -import java.util.List; - -import static java.util.stream.Collectors.toList; @CommandLine.Command(name = "available-permissions") @ParentCommand(value = RepositoryCommand.class) -class RepositoryPermissionsAvailableCommand implements Runnable { - - @CommandLine.Option(names = {"--roles", "-r"}, descriptionKey = "scm.repo.available-permissions.roles-only") - private boolean roles; - @CommandLine.Option(names = {"--verbs", "-v"}, descriptionKey = "scm.repo.available-permissions.verbs-only") - private boolean verbs; - - @CommandLine.Mixin - private final RepositoryTemplateRenderer templateRenderer; - private final RepositoryPermissionProvider repositoryPermissionProvider; - private final PermissionDescriptionResolver permissionDescriptionResolver; - private final RepositoryRoleManager repositoryRoleManager; +class RepositoryPermissionsAvailableCommand extends PermissionsAvailableCommand { @Inject public RepositoryPermissionsAvailableCommand(RepositoryTemplateRenderer templateRenderer, RepositoryPermissionProvider repositoryPermissionProvider, PermissionDescriptionResolver permissionDescriptionResolver, RepositoryRoleManager repositoryRoleManager) { - this.templateRenderer = templateRenderer; - this.repositoryPermissionProvider = repositoryPermissionProvider; - this.permissionDescriptionResolver = permissionDescriptionResolver; - this.repositoryRoleManager = repositoryRoleManager; - } - - @VisibleForTesting - void setRoles(boolean roles) { - this.roles = roles; - } - - @VisibleForTesting - void setVerbs(boolean verbs) { - this.verbs = verbs; - } - - @Override - public void run() { - if (roles) { - templateRenderer.renderRoles(getRoleBeans()); - } else if (verbs) { - templateRenderer.renderVerbs(getVerbBeans()); - } else { - templateRenderer.render(getRoleBeans(), getVerbBeans()); - } - } - - private List getVerbBeans() { - return repositoryPermissionProvider.availableVerbs().stream().map(this::createBean).collect(toList()); - } - - private List getRoleBeans() { - return repositoryRoleManager.getAll().stream().map(this::createBean).collect(toList()); - } - - private VerbBean createBean(String verb) { - return new VerbBean(verb, permissionDescriptionResolver.getDescription(verb).orElse(verb)); - } - - private RoleBean createBean(RepositoryRole role) { - return new RoleBean(role.getName(), role.getVerbs()); + super(templateRenderer, repositoryPermissionProvider, permissionDescriptionResolver, repositoryRoleManager); } } diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsClearCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsClearCommand.java index c27d26ecc4..4daddfe602 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsClearCommand.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsClearCommand.java @@ -27,6 +27,7 @@ package sonia.scm.repository.cli; import com.google.common.annotations.VisibleForTesting; import picocli.CommandLine; import sonia.scm.cli.ParentCommand; +import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryRoleManager; @@ -34,43 +35,23 @@ import javax.inject.Inject; @CommandLine.Command(name = "clear-permissions") @ParentCommand(value = RepositoryCommand.class) -class RepositoryPermissionsClearCommand extends RepositoryPermissionBaseCommand implements Runnable { +class RepositoryPermissionsClearCommand extends PermissionClearCommand { @CommandLine.Parameters(paramLabel = "namespace/name", index = "0", descriptionKey = "scm.repo.clear-permissions.repository") - private String repositoryName; - @CommandLine.Parameters(paramLabel = "name", index = "1", descriptionKey = "scm.repo.clear-permissions.name") - private String name; - - @CommandLine.Option(names = {"--group", "-g"}, descriptionKey = "scm.repo.clear-permissions.forGroup") - private boolean forGroup; + private String repositoryNamespaceAndName; @Inject public RepositoryPermissionsClearCommand(RepositoryManager repositoryManager, RepositoryRoleManager roleManager, RepositoryTemplateRenderer templateRenderer) { - super(repositoryManager, roleManager, templateRenderer); + super(roleManager, templateRenderer, new RepositoryPermissionBaseAdapter(repositoryManager, templateRenderer)); } @Override - public void run() { - modifyRepository( - repositoryName, - repo -> { - removeExistingPermission(repo, name, forGroup); - return true; - } - ); + String getIdentifier() { + return repositoryNamespaceAndName; } @VisibleForTesting - void setRepositoryName(String repositoryName) { - this.repositoryName = repositoryName; - } - - @VisibleForTesting - void setName(String name) { - this.name = name; - } - - public void setForGroup(boolean forGroup) { - this.forGroup = forGroup; + void setRepositoryNamespaceAndName(String repositoryNamespaceAndName) { + this.repositoryNamespaceAndName = repositoryNamespaceAndName; } } diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsListCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsListCommand.java index 26771872a5..166be9c28f 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsListCommand.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsListCommand.java @@ -28,102 +28,30 @@ import com.google.common.annotations.VisibleForTesting; import picocli.CommandLine; import sonia.scm.cli.CommandValidator; import sonia.scm.cli.ParentCommand; -import sonia.scm.cli.PermissionDescriptionResolver; -import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; -import sonia.scm.repository.RepositoryPermission; -import sonia.scm.repository.RepositoryRoleManager; import javax.inject.Inject; -import java.util.Collection; -import java.util.stream.Collectors; @CommandLine.Command(name = "list-permissions") @ParentCommand(value = RepositoryCommand.class) -class RepositoryPermissionsListCommand implements Runnable { - - @CommandLine.Mixin - private final RepositoryTemplateRenderer templateRenderer; - @CommandLine.Mixin - private final CommandValidator validator; - private final RepositoryManager manager; - private final RepositoryRoleManager roleManager; - private final PermissionDescriptionResolver permissionDescriptionResolver; +class RepositoryPermissionsListCommand extends PermissionsListCommand { @CommandLine.Parameters(paramLabel = "namespace/name", index = "0", descriptionKey = "scm.repo.list-permissions.repository") private String repository; - @CommandLine.Option(names = {"--verbose", "-v"}, descriptionKey = "scm.repo.list-permissions.verbose") - private boolean verbose; - @CommandLine.Option(names = {"--keys", "-k"}, descriptionKey = "scm.repo.list-permissions.keys") - private boolean keys; @Inject - public RepositoryPermissionsListCommand(RepositoryTemplateRenderer templateRenderer, CommandValidator validator, RepositoryManager manager, RepositoryRoleManager roleManager, PermissionDescriptionResolver permissionDescriptionResolver) { - this.templateRenderer = templateRenderer; - this.validator = validator; - this.manager = manager; - this.roleManager = roleManager; - this.permissionDescriptionResolver = permissionDescriptionResolver; + public RepositoryPermissionsListCommand(RepositoryTemplateRenderer templateRenderer, CommandValidator validator, RepositoryManager manager, RepositoryPermissionBeanMapper beanMapper) { + super(templateRenderer, validator, new RepositoryPermissionBaseAdapter(manager, templateRenderer), beanMapper); + } + + @Override + String getIdentifier() { + return repository; } @VisibleForTesting void setRepository(String repository) { this.repository = repository; } - - @VisibleForTesting - void setVerbose(boolean verbose) { - this.verbose = verbose; - } - - public void setKeys(boolean keys) { - this.keys = keys; - } - - @Override - public void run() { - validator.validate(); - String[] splitRepo = repository.split("/"); - if (splitRepo.length == 2) { - Repository repo = manager.get(new NamespaceAndName(splitRepo[0], splitRepo[1])); - - if (repo != null) { - Collection permissions = - repo.getPermissions().stream().map(this::createPermissionBean).collect(Collectors.toList()); - if (verbose) { - templateRenderer.renderVerbose(permissions); - } else { - templateRenderer.render(permissions); - } - } else { - templateRenderer.renderNotFoundError(); - } - } else { - templateRenderer.renderInvalidInputError(); - } - } - - private RepositoryPermissionBean createPermissionBean(RepositoryPermission permission) { - Collection effectiveVerbs; - if (permission.getRole() == null) { - effectiveVerbs = permission.getVerbs(); - } else { - effectiveVerbs = roleManager.get(permission.getRole()).getVerbs(); - } - return new RepositoryPermissionBean( - permission.isGroupPermission(), - permission.getName(), - permission.getRole() == null? "CUSTOM": permission.getRole(), - keys? effectiveVerbs: getDescriptions(effectiveVerbs) - ); - } - - private Collection getDescriptions(Collection effectiveVerbs) { - return effectiveVerbs.stream().map(this::getDescription).collect(Collectors.toList()); - } - - private String getDescription(String verb) { - return permissionDescriptionResolver.getDescription(verb).orElse(verb); - } } diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsRemoveCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsRemoveCommand.java index 0bb68f6b66..ed96ff4f6b 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsRemoveCommand.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsRemoveCommand.java @@ -27,66 +27,31 @@ package sonia.scm.repository.cli; import com.google.common.annotations.VisibleForTesting; import picocli.CommandLine; import sonia.scm.cli.ParentCommand; +import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; -import sonia.scm.repository.RepositoryPermission; import sonia.scm.repository.RepositoryRoleManager; import javax.inject.Inject; -import java.util.Set; - -import static java.util.Arrays.asList; @CommandLine.Command(name = "remove-permissions") @ParentCommand(value = RepositoryCommand.class) -class RepositoryPermissionsRemoveCommand extends RepositoryPermissionBaseCommand implements Runnable { +class RepositoryPermissionsRemoveCommand extends PermissionsRemoveCommand { @CommandLine.Parameters(paramLabel = "namespace/name", index = "0", descriptionKey = "scm.repo.remove-permissions.repository") - private String repositoryName; - @CommandLine.Parameters(paramLabel = "name", index = "1", descriptionKey = "scm.repo.remove-permissions.name") - private String name; - @CommandLine.Parameters(paramLabel = "verbs", index = "2..", arity = "1..", descriptionKey = "scm.repo.remove-permissions.verbs") - private String[] verbs = new String[0]; - - @CommandLine.Option(names = {"--group", "-g"}, descriptionKey = "scm.repo.remove-permissions.forGroup") - private boolean forGroup; + private String repositoryNamespaceAndName; @Inject public RepositoryPermissionsRemoveCommand(RepositoryManager repositoryManager, RepositoryRoleManager roleManager, RepositoryTemplateRenderer templateRenderer) { - super(repositoryManager, roleManager, templateRenderer); + super(roleManager, templateRenderer, new RepositoryPermissionBaseAdapter(repositoryManager, templateRenderer)); } @Override - public void run() { - modifyRepository( - repositoryName, - repository -> { - Set resultingVerbs = - getPermissionsAsModifiableSet(repository, name, forGroup); - if (resultingVerbs.stream().noneMatch(verb -> asList(verbs).contains(verb))) { - return false; - } - resultingVerbs.removeAll(asList(this.verbs)); - replacePermission(repository, new RepositoryPermission(name, resultingVerbs, forGroup)); - return true; - } - ); + String getIdentifier() { + return repositoryNamespaceAndName; } @VisibleForTesting - void setRepositoryName(String repositoryName) { - this.repositoryName = repositoryName; - } - - @VisibleForTesting - void setName(String name) { - this.name = name; - } - - public void setVerbs(String... verbs) { - this.verbs = verbs; - } - - public void setForGroup(boolean forGroup) { - this.forGroup = forGroup; + void setRepositoryNamespaceAndName(String repositoryNamespaceAndName) { + this.repositoryNamespaceAndName = repositoryNamespaceAndName; } } diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsSetRoleCommand.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsSetRoleCommand.java index 2b4ae8e7cf..4ff1234605 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsSetRoleCommand.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryPermissionsSetRoleCommand.java @@ -27,65 +27,31 @@ package sonia.scm.repository.cli; import com.google.common.annotations.VisibleForTesting; import picocli.CommandLine; import sonia.scm.cli.ParentCommand; +import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; -import sonia.scm.repository.RepositoryPermission; import sonia.scm.repository.RepositoryRoleManager; import javax.inject.Inject; @CommandLine.Command(name = "set-role") @ParentCommand(value = RepositoryCommand.class) -class RepositoryPermissionsSetRoleCommand extends RepositoryPermissionBaseCommand implements Runnable { - - private final RepositoryRoleManager roleManager; +class RepositoryPermissionsSetRoleCommand extends PermissionsSetRoleCommand { @CommandLine.Parameters(paramLabel = "namespace/name", index = "0", descriptionKey = "scm.repo.set-role.repository") - private String repositoryName; - @CommandLine.Parameters(paramLabel = "name", index = "1", descriptionKey = "scm.repo.set-role.name") - private String name; - @CommandLine.Parameters(paramLabel = "role", index = "2", descriptionKey = "scm.repo.set-role.role") - private String role; - - @CommandLine.Option(names = {"--group", "-g"}, descriptionKey = "scm.repo.set-role.forGroup") - private boolean forGroup; + private String repositoryNamespaceAndName; @Inject public RepositoryPermissionsSetRoleCommand(RepositoryManager repositoryManager, RepositoryRoleManager roleManager, RepositoryTemplateRenderer templateRenderer) { - super(repositoryManager, roleManager, templateRenderer); - this.roleManager = roleManager; + super(roleManager, templateRenderer, new RepositoryPermissionBaseAdapter(repositoryManager, templateRenderer)); } @Override - public void run() { - modifyRepository( - repositoryName, - repository -> { - if (roleManager.get(role) == null) { - renderRoleNotFoundError(); - return false; - } - replacePermission(repository, new RepositoryPermission(name, role, forGroup)); - return true; - } - ); + protected String getIdentifier() { + return repositoryNamespaceAndName; } @VisibleForTesting - void setRepositoryName(String repositoryName) { - this.repositoryName = repositoryName; - } - - @VisibleForTesting - void setName(String name) { - this.name = name; - } - - @VisibleForTesting - void setRole(String role) { - this.role = role; - } - - public void setForGroup(boolean forGroup) { - this.forGroup = forGroup; + void setRepositoryNamespaceAndName(String repositoryNamespaceAndName) { + this.repositoryNamespaceAndName = repositoryNamespaceAndName; } } diff --git a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryTemplateRenderer.java b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryTemplateRenderer.java index 23e0f47c3d..43f09ddc19 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryTemplateRenderer.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/cli/RepositoryTemplateRenderer.java @@ -131,8 +131,8 @@ class RepositoryTemplateRenderer extends TemplateRenderer { context.exit(ExitCode.NOT_FOUND); } - void renderVerbNotFoundError() { - renderToStderr("{{i18n.verbNotFound}}", emptyMap()); + void renderVerbNotFoundError(String verb) { + renderToStderr("{{i18n.verbNotFound}}: {{verb}}", Map.of("verb", verb)); context.getStderr().println(); context.exit(ExitCode.NOT_FOUND); } diff --git a/scm-webapp/src/main/resources/sonia/scm/cli/i18n.properties b/scm-webapp/src/main/resources/sonia/scm/cli/i18n.properties index c284c66b34..14f3705dc4 100644 --- a/scm-webapp/src/main/resources/sonia/scm/cli/i18n.properties +++ b/scm-webapp/src/main/resources/sonia/scm/cli/i18n.properties @@ -109,7 +109,7 @@ scm.repo.set-role.name = User or group name to set role for scm.repo.set-role.role = Permission role to grant to user or group scm.repo.set-role.forGroup = Set role for a group, not for a user -scm.repo.add-permissions.usage.description.0 = Add single permission for user or group +scm.repo.add-permissions.usage.description.0 = Add single permissions for user or group scm.repo.add-permissions.repository = Repository namespace/name scm.repo.add-permissions.name = User or group name to grant permission to scm.repo.add-permissions.verbs = Single permissions to grant to user or group @@ -139,6 +139,27 @@ scm.repo.permissions.description = Description roleNotFound= Could not find permission role verbNotFound= Could not find single permission +## Namespace +scm.namespace.usage.description.0 = Resource command for all namespace-related actions +scm.namespace.list.usage.description.0 = List all namespaces on server + +scm.namespace.available-permissions.usage.description.0 = List available permissions for namespace + +scm.namespace.list-permissions.usage.description.0 = List permissions for namespace +scm.namespace.list-permissions.namespace = Namespace + +scm.namespace.add-permissions.usage.description.0 = Add single permissions for user or group +scm.namespace.add-permissions.namespace = Namespace + +scm.namespace.set-role.usage.description.0 = Set role permission for user or group +scm.namespace.set-role.namespace = Namespace + +scm.namespace.remove-permissions.usage.description.0 = Revoke single permission from user or group +scm.namespace.remove-permissions.namespace = Namespace + +scm.namespace.clear-permissions.usage.description.0 = Revoke all permissions from user or group +scm.namespace.clear-permissions.namespace = Namespace + ## User scm.user.username = Username scm.user.displayName = Display Name diff --git a/scm-webapp/src/main/resources/sonia/scm/cli/i18n_de.properties b/scm-webapp/src/main/resources/sonia/scm/cli/i18n_de.properties index 459d5ace58..2f05198b4c 100644 --- a/scm-webapp/src/main/resources/sonia/scm/cli/i18n_de.properties +++ b/scm-webapp/src/main/resources/sonia/scm/cli/i18n_de.properties @@ -109,7 +109,7 @@ scm.repo.set-role.name = Name des Benutzers oder der Gruppe f scm.repo.set-role.role = Rolle, die für den Benutzer oder die Gruppe gesetzt werden soll scm.repo.set-role.forGroup = Setzt die Berechtigung für eine Gruppe, nicht für einen Benutzer -scm.repo.add-permissions.usage.description.0 = Fügt eine einzelne Berechtigung für einen Benutzer oder eine Gruppe hinzu +scm.repo.add-permissions.usage.description.0 = Fügt einzelne Berechtigungen für einen Benutzer oder eine Gruppe hinzu scm.repo.add-permissions.repository = Repository Namespace/Name scm.repo.add-permissions.name = Name des Benutzers oder der Gruppe für die Berechtigung scm.repo.add-permissions.verbs = Einzelne Berechtigungen, die für den Benutzer oder die Gruppe hinzugefügt werden sollen @@ -137,7 +137,28 @@ scm.repo.permissions.verb = Verb scm.repo.permissions.description = Beschreibung roleNotFound= Berechtigungsrolle konnte nicht gefunden werden -verbNotFound= konnte nicht gefunden werden +verbNotFound= Berechtigung konnte nicht gefunden werden + +## Namespace +scm.namespace.usage.description.0 = Ressourcenkommando für alle Aktionen zu Namespaces +scm.namespace.list.usage.description.0 = Listet alle Namespaces + +scm.namespace.available-permissions.usage.description.0 = Listet verfügbare Berechtigungen für den Namespace + +scm.namespace.list-permissions.usage.description.0 = Listet die Berechtigungen für den Namespace +scm.namespace.list-permissions.namespace = Namespace + +scm.namespace.add-permissions.usage.description.0 = Fügt einzelne Berechtigungen für einen Benutzer oder eine Gruppe hinzu +scm.namespace.add-permissions.namespace = Namespace + +scm.namespace.set-role.usage.description.0 = Setzt eine Berechtigungesrolle für einen Benutzer oder eine Gruppe +scm.namespace.set-role.namespace = Namespace + +scm.namespace.remove-permissions.usage.description.0 = Entzieht einem Benutzer oder einer Gruppe einzelne Berechtigungen +scm.namespace.remove-permissions.namespace = Namespace + +scm.namespace.clear-permissions.usage.description.0 = Entzieht einem Benutzer oder einer Gruppe alle Berechtigungen +scm.namespace.clear-permissions.namespace = Namespace ## User scm.user.username = Benutzername diff --git a/scm-webapp/src/test/java/sonia/scm/repository/cli/NamespacePermissionsListCommandTest.java b/scm-webapp/src/test/java/sonia/scm/repository/cli/NamespacePermissionsListCommandTest.java new file mode 100644 index 0000000000..b9a79d9488 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/repository/cli/NamespacePermissionsListCommandTest.java @@ -0,0 +1,204 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.cli; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.cli.CommandValidator; +import sonia.scm.cli.PermissionDescriptionResolver; +import sonia.scm.repository.Namespace; +import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.NamespaceManager; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryManager; +import sonia.scm.repository.RepositoryPermission; +import sonia.scm.repository.RepositoryRole; +import sonia.scm.repository.RepositoryRoleManager; +import sonia.scm.repository.RepositoryTestData; + +import java.util.Collection; +import java.util.List; + +import static java.util.Optional.of; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class NamespacePermissionsListCommandTest { + + @Mock + private RepositoryTemplateRenderer templateRenderer; + @Mock + private CommandValidator validator; + @Mock + private NamespaceManager manager; + @Mock + private RepositoryRoleManager roleManager; + @Mock + private PermissionDescriptionResolver permissionDescriptionResolver; + + @InjectMocks + private RepositoryPermissionBeanMapper beanMapper; + + private NamespacePermissionsListCommand command; + + @BeforeEach + void initCommand() { + command = new NamespacePermissionsListCommand(templateRenderer, validator, manager, beanMapper); + } + + @Test + void shouldPrintNotFoundErrorForUnknownRepository() { + command.setNamespace("hg2g"); + + command.run(); + + verify(templateRenderer).renderNotFoundError(); + } + + @Nested + class ForExistingRepository { + + @Captor + private ArgumentCaptor> permissionsCaptor; + + private final Namespace namespace = new Namespace("hitchhiker"); + + @BeforeEach + void mockRepository() { + when(manager.get("hitchhiker")) + .thenReturn(of(namespace)); + command.setNamespace("hitchhiker"); + } + + @Nested + class WithoutVerboseFlag { + + @BeforeEach + void setUpRenderer() { + doNothing().when(templateRenderer).render(permissionsCaptor.capture()); + } + + @Test + void shouldRenderEmptyTableWithoutPermissions() { + command.run(); + + Collection beans = permissionsCaptor.getValue(); + assertThat(beans).isEmpty(); + } + + @Test + void shouldListCustomUserPermission() { + RepositoryPermission permission = new RepositoryPermission("trillian", List.of("read", "write"), false); + namespace.setPermissions(List.of(permission)); + when(permissionDescriptionResolver.getDescription("read")) + .thenReturn(of("read repository")); + when(permissionDescriptionResolver.getDescription("write")) + .thenReturn(of("write repository")); + + command.run(); + + Collection beans = permissionsCaptor.getValue(); + assertThat(beans).extracting("groupPermission", "name", "role") + .containsExactly(tuple(false, "trillian", "CUSTOM")); + } + } + + @Nested + class WithVerboseFlag { + + @BeforeEach + void setUpRenderer() { + doNothing().when(templateRenderer).renderVerbose(permissionsCaptor.capture()); + } + + @BeforeEach + void setVerbose() { + command.setVerbose(true); + } + + @Test + void shouldListUserPermissionWithVerbs() { + RepositoryPermission permission = new RepositoryPermission("trillian", List.of("read", "write"), false); + namespace.setPermissions(List.of(permission)); + when(permissionDescriptionResolver.getDescription("read")) + .thenReturn(of("read repository")); + when(permissionDescriptionResolver.getDescription("write")) + .thenReturn(of("write repository")); + + command.run(); + + Collection beans = permissionsCaptor.getValue(); + assertThat(beans).extracting("groupPermission", "name", "role", "verbs") + .containsExactly(tuple(false, "trillian", "CUSTOM", List.of("read repository", "write repository"))); + } + + @Test + void shouldListUserPermissionWithRole() { + RepositoryPermission permission = new RepositoryPermission("trillian", "READ", false); + namespace.setPermissions(List.of(permission)); + when(roleManager.get("READ")) + .thenReturn(new RepositoryRole("READ", List.of("read", "pull"), "")); + when(permissionDescriptionResolver.getDescription("read")) + .thenReturn(of("read repository")); + when(permissionDescriptionResolver.getDescription("pull")) + .thenReturn(of("clone/checkout repository")); + + command.run(); + + Collection beans = permissionsCaptor.getValue(); + assertThat(beans).extracting("groupPermission", "name", "verbs") + .containsExactly(tuple(false, "trillian", List.of("read repository", "clone/checkout repository"))); + } + + @Test + void shouldListUserPermissionWithVerbsAsKeys() { + RepositoryPermission permission = new RepositoryPermission("trillian", List.of("read", "write"), false); + namespace.setPermissions(List.of(permission)); + + command.setKeys(true); + command.run(); + + Collection beans = permissionsCaptor.getValue(); + assertThat(beans).extracting("groupPermission", "name", "role") + .containsExactly(tuple(false, "trillian", "CUSTOM")); + assertThat(beans).extracting("verbs") + .map(c -> ((Collection) c).stream().collect(toList())) // to satisfy equal in the comparison, we have to use this form + .containsExactly(List.of("read", "write")); + } + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsAddCommandTest.java b/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsAddCommandTest.java index ddf945befe..75279f2573 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsAddCommandTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsAddCommandTest.java @@ -43,7 +43,6 @@ import sonia.scm.repository.RepositoryTestData; import java.util.List; import java.util.Set; -import static java.util.Optional.empty; import static java.util.Optional.of; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; @@ -91,7 +90,7 @@ class RepositoryPermissionsAddCommandTest { @Test void shouldSetMultipleVerbsForNewUser() { - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("trillian"); command.setVerbs("read", "pull", "push"); @@ -112,7 +111,7 @@ class RepositoryPermissionsAddCommandTest { ) ); - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("trillian"); command.setVerbs("write"); @@ -133,7 +132,7 @@ class RepositoryPermissionsAddCommandTest { ) ); - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("hog"); command.setVerbs("write"); command.setForGroup(true); @@ -157,7 +156,7 @@ class RepositoryPermissionsAddCommandTest { when(roleManager.get("READ")) .thenReturn(new RepositoryRole("READ", List.of("read", "pull"), "")); - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("trillian"); command.setVerbs("write"); @@ -180,7 +179,7 @@ class RepositoryPermissionsAddCommandTest { when(roleManager.get("READ")) .thenReturn(new RepositoryRole("READ", List.of("read", "pull"), "")); - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("trillian"); command.setVerbs("read"); @@ -192,19 +191,19 @@ class RepositoryPermissionsAddCommandTest { @Test void shouldHandleMissingVerb() { - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("trillian"); command.setVerbs("make-party"); command.run(); - verify(templateRenderer).renderVerbNotFoundError(); + verify(templateRenderer).renderVerbNotFoundError("make-party"); } } @Test void shouldHandleIllegalNamespaceNameParameter() { - command.setRepositoryName("illegal name"); + command.setRepositoryNamespaceAndName("illegal name"); command.setName("trillian"); command.setVerbs("write"); @@ -215,7 +214,7 @@ class RepositoryPermissionsAddCommandTest { @Test void shouldHandleNotExistingRepository() { - command.setRepositoryName("no/repository"); + command.setRepositoryNamespaceAndName("no/repository"); command.setName("trillian"); command.setVerbs("write"); diff --git a/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsClearCommandTest.java b/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsClearCommandTest.java index 8ca98a6512..8dbbbc7cf6 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsClearCommandTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsClearCommandTest.java @@ -74,7 +74,7 @@ class RepositoryPermissionsClearCommandTest { ) ); - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("trillian"); command.run(); @@ -93,7 +93,7 @@ class RepositoryPermissionsClearCommandTest { ) ); - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("hog"); command.setForGroup(true); @@ -108,7 +108,7 @@ class RepositoryPermissionsClearCommandTest { @Test void shouldHandleIllegalNamespaceNameParameter() { - command.setRepositoryName("illegal name"); + command.setRepositoryNamespaceAndName("illegal name"); command.setName("trillian"); command.run(); @@ -118,7 +118,7 @@ class RepositoryPermissionsClearCommandTest { @Test void shouldHandleNotExistingRepository() { - command.setRepositoryName("no/repository"); + command.setRepositoryNamespaceAndName("no/repository"); command.setName("trillian"); command.run(); diff --git a/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsListCommandTest.java b/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsListCommandTest.java index 6ec43f8bde..ca07451551 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsListCommandTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsListCommandTest.java @@ -69,8 +69,15 @@ class RepositoryPermissionsListCommandTest { private PermissionDescriptionResolver permissionDescriptionResolver; @InjectMocks + private RepositoryPermissionBeanMapper beanMapper; + private RepositoryPermissionsListCommand command; + @BeforeEach + void initCommand() { + command = new RepositoryPermissionsListCommand(templateRenderer, validator, manager, beanMapper); + } + @Test void shouldPrintNotFoundErrorForUnknownRepository() { command.setRepository("hg2g/hog"); diff --git a/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsRemoveCommandTest.java b/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsRemoveCommandTest.java index cbed86d911..842d52f51a 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsRemoveCommandTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsRemoveCommandTest.java @@ -82,7 +82,7 @@ class RepositoryPermissionsRemoveCommandTest { ) ); - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("dent"); command.setVerbs("write", "push", "pull"); @@ -103,7 +103,7 @@ class RepositoryPermissionsRemoveCommandTest { ) ); - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("hog"); command.setVerbs("write"); command.setForGroup(true); @@ -127,7 +127,7 @@ class RepositoryPermissionsRemoveCommandTest { when(roleManager.get("READ")) .thenReturn(new RepositoryRole("READ", List.of("read", "pull"), "")); - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("dent"); command.setVerbs("pull"); @@ -148,7 +148,7 @@ class RepositoryPermissionsRemoveCommandTest { ) ); - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("dent"); command.setVerbs("push", "pull"); @@ -160,7 +160,7 @@ class RepositoryPermissionsRemoveCommandTest { @Test void shouldHandleIllegalNamespaceNameParameter() { - command.setRepositoryName("illegal name"); + command.setRepositoryNamespaceAndName("illegal name"); command.setName("trillian"); command.setVerbs("write"); @@ -171,7 +171,7 @@ class RepositoryPermissionsRemoveCommandTest { @Test void shouldHandleNotExistingRepository() { - command.setRepositoryName("no/repository"); + command.setRepositoryNamespaceAndName("no/repository"); command.setName("trillian"); command.setVerbs("write"); diff --git a/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsSetRoleCommandTest.java b/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsSetRoleCommandTest.java index a35fd93682..15c443809a 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsSetRoleCommandTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/cli/RepositoryPermissionsSetRoleCommandTest.java @@ -85,7 +85,7 @@ class RepositoryPermissionsSetRoleCommandTest { @Test void shouldSetRoleForUser() { - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("trillian"); command.setRole("OWNER"); @@ -100,7 +100,7 @@ class RepositoryPermissionsSetRoleCommandTest { @Test void shouldSetRoleForGroup() { - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("crew"); command.setRole("READ"); command.setForGroup(true); @@ -122,7 +122,7 @@ class RepositoryPermissionsSetRoleCommandTest { ) ); - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("trillian"); command.setRole("OWNER"); @@ -143,7 +143,7 @@ class RepositoryPermissionsSetRoleCommandTest { ) ); - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("trillian"); command.setRole("OWNER"); command.setForGroup(true); @@ -160,7 +160,7 @@ class RepositoryPermissionsSetRoleCommandTest { @Test void shouldHandleMissingRole() { - command.setRepositoryName("hitchhiker/HeartOfGold"); + command.setRepositoryNamespaceAndName("hitchhiker/HeartOfGold"); command.setName("trillian"); command.setRole("FUNNY"); @@ -172,7 +172,7 @@ class RepositoryPermissionsSetRoleCommandTest { @Test void shouldHandleIllegalNamespaceNameParameter() { - command.setRepositoryName("illegal name"); + command.setRepositoryNamespaceAndName("illegal name"); command.setName("trillian"); command.setRole("READ"); @@ -184,7 +184,7 @@ class RepositoryPermissionsSetRoleCommandTest { @Test void shouldHandleNotExistingRepository() { - command.setRepositoryName("no/repository"); + command.setRepositoryNamespaceAndName("no/repository"); command.setName("trillian"); command.setRole("READ");