From 049b5ba54cf583bab20c7e88681ae9ee94dfdfa2 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Thu, 21 Jul 2022 09:56:47 +0200 Subject: [PATCH] Add CLI commands for global permission management (#2091) Add CLI commands for permission management for users and groups. Co-authored-by: Konstantin Schaper --- gradle/changelog/cli_global_permissions.yaml | 2 + .../cli/PermissionDescriptionResolver.java | 90 ++++++++++++++- .../group/cli/GroupPermissionAddCommand.java | 94 ++++++++++++++++ .../cli/GroupPermissionAvailableCommand.java | 73 ++++++++++++ .../cli/GroupPermissionClearCommand.java | 72 ++++++++++++ .../group/cli/GroupPermissionListCommand.java | 106 ++++++++++++++++++ .../cli/GroupPermissionRemoveCommand.java | 93 +++++++++++++++ .../scm/group/cli/GroupTemplateRenderer.java | 22 +++- .../user/cli/UserPermissionAddCommand.java | 94 ++++++++++++++++ .../cli/UserPermissionAvailableCommand.java | 73 ++++++++++++ .../user/cli/UserPermissionClearCommand.java | 72 ++++++++++++ .../user/cli/UserPermissionListCommand.java | 104 +++++++++++++++++ .../user/cli/UserPermissionRemoveCommand.java | 93 +++++++++++++++ .../scm/user/cli/UserTemplateRenderer.java | 40 +++++-- .../resources/sonia/scm/cli/i18n.properties | 42 ++++++- .../sonia/scm/cli/i18n_de.properties | 39 ++++++- .../group/cli/GroupAddMemberCommandTest.java | 2 +- .../scm/group/cli/GroupGetCommandTest.java | 2 +- .../scm/group/cli/GroupModifyCommandTest.java | 2 +- .../cli/GroupPermissionAddCommandTest.java | 102 +++++++++++++++++ .../GroupPermissionAvailableCommandTest.java | 72 ++++++++++++ .../cli/GroupPermissionClearCommandTest.java | 83 ++++++++++++++ .../cli/GroupPermissionListCommandTest.java | 99 ++++++++++++++++ .../cli/GroupPermissionRemoveCommandTest.java | 91 +++++++++++++++ .../cli/GroupRemoveMemberCommandTest.java | 2 +- .../cli/UserPermissionAddCommandTest.java | 102 +++++++++++++++++ .../UserPermissionAvailableCommandTest.java | 73 ++++++++++++ .../cli/UserPermissionClearCommandTest.java | 83 ++++++++++++++ .../cli/UserPermissionListCommandTest.java | 98 ++++++++++++++++ .../cli/UserPermissionRemoveCommandTest.java | 91 +++++++++++++++ 30 files changed, 1992 insertions(+), 19 deletions(-) create mode 100644 gradle/changelog/cli_global_permissions.yaml create mode 100644 scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionAddCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionAvailableCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionClearCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionListCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionRemoveCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionAddCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionAvailableCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionClearCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionListCommand.java create mode 100644 scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionRemoveCommand.java create mode 100644 scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionAddCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionAvailableCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionClearCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionListCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionRemoveCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionAddCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionAvailableCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionClearCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionListCommandTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionRemoveCommandTest.java diff --git a/gradle/changelog/cli_global_permissions.yaml b/gradle/changelog/cli_global_permissions.yaml new file mode 100644 index 0000000000..b81019be65 --- /dev/null +++ b/gradle/changelog/cli_global_permissions.yaml @@ -0,0 +1,2 @@ +- type: added + description: CLI commands for user/group permission management ([#2091](https://github.com/scm-manager/scm-manager/pull/2091)) diff --git a/scm-webapp/src/main/java/sonia/scm/cli/PermissionDescriptionResolver.java b/scm-webapp/src/main/java/sonia/scm/cli/PermissionDescriptionResolver.java index 953d8659e9..6e956e8936 100644 --- a/scm-webapp/src/main/java/sonia/scm/cli/PermissionDescriptionResolver.java +++ b/scm-webapp/src/main/java/sonia/scm/cli/PermissionDescriptionResolver.java @@ -35,6 +35,8 @@ import static java.util.Optional.empty; import static java.util.Optional.of; public class PermissionDescriptionResolver { + + private static final String DISPLAYNAME = "displayName"; private final I18nCollector i18nCollector; private final Locale locale; @@ -44,12 +46,21 @@ public class PermissionDescriptionResolver { } public Optional getDescription(String verb) { + collectI18nJson(); + return getVerbDescriptionFromI18nBundle(verb); + } + + public Optional getGlobalDescription(String verb) { + collectI18nJson(); + return getGlobalVerbDescriptionFromI18nBundle(verb); + } + + private void collectI18nJson() { try { i18nCollector.findJson(locale.getLanguage()); } catch (IOException e) { throw new RuntimeException("failed to load i18n package", e); } - return getVerbDescriptionFromI18nBundle(verb); } private Optional getVerbDescriptionFromI18nBundle(String verb) { @@ -74,11 +85,86 @@ public class PermissionDescriptionResolver { if (permissionNode == null) { return empty(); } - JsonNode displayNameNode = permissionNode.get("displayName"); + JsonNode displayNameNode = permissionNode.get(DISPLAYNAME); if (displayNameNode == null) { return empty(); } return of(displayNameNode.asText()); } + + private Optional getGlobalVerbDescriptionFromI18nBundle(String verb) { + String[] verbParts = verb.split(":"); + + Optional jsonNode; + try { + jsonNode = i18nCollector.findJson(locale.getLanguage()); + } catch (IOException e) { + return empty(); + } + if (jsonNode.isEmpty()) { + return empty(); + } + JsonNode permissionsNode = jsonNode.get().get("permissions"); + if (permissionsNode == null) { + return empty(); + } + if (verbParts.length == 1) { + return resolveSinglePartPermission(verb, permissionsNode); + } else if (verbParts.length == 2) { + return resolveTwoPartPermission(verbParts, permissionsNode); + } else if (verbParts.length == 3) { + return resolveThreePartPermission(verbParts, permissionsNode); + } + + return empty(); + } + + private Optional resolveSinglePartPermission(String verb, JsonNode permissionsNode) { + JsonNode firstNode = permissionsNode.get(verb); + if (firstNode == null) { + return empty(); + } + JsonNode displayNameNode = firstNode.get(DISPLAYNAME); + if (displayNameNode == null) { + return empty(); + } + return of(displayNameNode.asText()); + } + + private Optional resolveTwoPartPermission(String[] verbParts, JsonNode permissionsNode) { + JsonNode firstNode = permissionsNode.get(verbParts[0]); + if (firstNode == null) { + return empty(); + } + JsonNode secondNode = firstNode.get(verbParts[1]); + if (secondNode == null) { + return empty(); + } + JsonNode displayNameNode = secondNode.get(DISPLAYNAME); + if (displayNameNode == null) { + return empty(); + } + return of(displayNameNode.asText()); + } + + private Optional resolveThreePartPermission(String[] verbParts, JsonNode permissionsNode) { + JsonNode firstNode = permissionsNode.get(verbParts[0]); + if (firstNode == null) { + return empty(); + } + JsonNode secondNode = firstNode.get(verbParts[1]); + if (secondNode == null) { + return empty(); + } + JsonNode thirdNode = secondNode.get(verbParts[2]); + if (thirdNode == null) { + return empty(); + } + JsonNode displayNameNode = thirdNode.get(DISPLAYNAME); + if (displayNameNode == null) { + return empty(); + } + return of(displayNameNode.asText()); + } } diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionAddCommand.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionAddCommand.java new file mode 100644 index 0000000000..cadd51192e --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionAddCommand.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.group.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; + +import javax.inject.Inject; +import java.util.Collection; + +@ParentCommand(value = GroupCommand.class) +@CommandLine.Command(name = "add-permissions") +class GroupPermissionAddCommand implements Runnable { + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.group.name") + private String name; + + @CommandLine.Parameters(index = "1..", arity = "1..", paramLabel = "", descriptionKey = "scm.group.permissions") + private String[] addedPermissions; + + @CommandLine.Mixin + private final GroupTemplateRenderer templateRenderer; + private final PermissionAssigner permissionAssigner; + private final GroupManager groupManager; + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @Inject + GroupPermissionAddCommand(GroupTemplateRenderer templateRenderer, PermissionAssigner permissionAssigner, GroupManager groupManager) { + this.templateRenderer = templateRenderer; + this.permissionAssigner = permissionAssigner; + this.groupManager = groupManager; + } + + @Override + public void run() { + Group group = groupManager.get(name); + if (group == null) { + templateRenderer.renderNotFoundError(); + return; + } + Collection permissions = permissionAssigner.readPermissionsForGroup(name); + for (String addedPermission : addedPermissions) { + if (isPermissionInvalid(addedPermission)) { + templateRenderer.renderUnknownPermissionError(addedPermission); + return; + } + permissions.add(new PermissionDescriptor(addedPermission)); + } + permissionAssigner.setPermissionsForGroup(name, permissions); + } + + private boolean isPermissionInvalid(String permission) { + return permissionAssigner.getAvailablePermissions() + .stream() + .noneMatch(p -> p.getValue().equals(permission)); + } + + @VisibleForTesting + void setName(String name) { + this.name = name; + } + @VisibleForTesting + void setAddedPermissions(String[] addedPermissions) { + this.addedPermissions = addedPermissions; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionAvailableCommand.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionAvailableCommand.java new file mode 100644 index 0000000000..55650e8b67 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionAvailableCommand.java @@ -0,0 +1,73 @@ +/* + * 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.group.cli; + +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.cli.PermissionDescriptionResolver; +import sonia.scm.cli.Table; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; + +import javax.inject.Inject; +import java.util.Collection; +import java.util.Map; + +@ParentCommand(value = GroupCommand.class) +@CommandLine.Command(name = "available-permissions") +class GroupPermissionAvailableCommand implements Runnable { + + private static final String TABLE_TEMPLATE = String.join("\n", + "{{#rows}}", + "{{#cols}}{{#row.first}}{{#upper}}{{value}}{{/upper}}{{/row.first}}{{^row.first}}{{value}}{{/row.first}}{{^last}} {{/last}}{{/cols}}", + "{{/rows}}" + ); + + @CommandLine.Mixin + private final GroupTemplateRenderer templateRenderer; + private final PermissionAssigner permissionAssigner; + private final PermissionDescriptionResolver descriptionResolver; + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @Inject + GroupPermissionAvailableCommand(GroupTemplateRenderer templateRenderer, PermissionAssigner permissionAssigner, PermissionDescriptionResolver descriptionResolver) { + this.templateRenderer = templateRenderer; + this.permissionAssigner = permissionAssigner; + this.descriptionResolver = descriptionResolver; + } + + @Override + public void run() { + Collection availablePermissions = permissionAssigner.getAvailablePermissions(); + Table table = templateRenderer.createTable(); + table.addHeader("value", "description"); + for (PermissionDescriptor descriptor : availablePermissions) { + String verb = descriptor.getValue(); + table.addRow(verb, descriptionResolver.getGlobalDescription(verb).orElse(verb)); + } + templateRenderer.renderToStdout(TABLE_TEMPLATE, Map.of("rows", table, "permissions", availablePermissions)); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionClearCommand.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionClearCommand.java new file mode 100644 index 0000000000..e253dd9837 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionClearCommand.java @@ -0,0 +1,72 @@ +/* + * 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.group.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.security.PermissionAssigner; + +import javax.inject.Inject; +import java.util.Collections; + +@ParentCommand(value = GroupCommand.class) +@CommandLine.Command(name = "clear-permissions") +class GroupPermissionClearCommand implements Runnable { + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.group.name") + private String name; + + @CommandLine.Mixin + private final GroupTemplateRenderer templateRenderer; + private final PermissionAssigner permissionAssigner; + private final GroupManager groupManager; + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @Inject + GroupPermissionClearCommand(GroupTemplateRenderer templateRenderer, PermissionAssigner permissionAssigner, GroupManager groupManager) { + this.templateRenderer = templateRenderer; + this.permissionAssigner = permissionAssigner; + this.groupManager = groupManager; + } + + @Override + public void run() { + Group group = groupManager.get(name); + if (group == null) { + templateRenderer.renderNotFoundError(); + return; + } + permissionAssigner.setPermissionsForGroup(name, Collections.emptyList()); + } + + @VisibleForTesting + void setName(String name) { + this.name = name; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionListCommand.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionListCommand.java new file mode 100644 index 0000000000..4def5f6cc2 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionListCommand.java @@ -0,0 +1,106 @@ +/* + * 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.group.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.cli.PermissionDescriptionResolver; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; + +import javax.inject.Inject; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +@ParentCommand(value = GroupCommand.class) +@CommandLine.Command(name = "list-permissions") +class GroupPermissionListCommand implements Runnable { + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.group.name") + private String name; + + @CommandLine.Option(names = {"--keys", "-k"}, descriptionKey = "scm.group.list-permissions.keys") + private boolean keys; + + @CommandLine.Mixin + private final GroupTemplateRenderer templateRenderer; + private final PermissionAssigner permissionAssigner; + private final PermissionDescriptionResolver descriptionResolver; + private final GroupManager groupManager; + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + + @Inject + GroupPermissionListCommand(GroupTemplateRenderer templateRenderer, PermissionAssigner permissionAssigner, PermissionDescriptionResolver descriptionResolver, GroupManager groupManager) { + this.templateRenderer = templateRenderer; + this.permissionAssigner = permissionAssigner; + this.descriptionResolver = descriptionResolver; + this.groupManager = groupManager; + } + + + @Override + public void run() { + Collection permissions; + Group group = groupManager.get(name); + if (group == null) { + templateRenderer.renderNotFoundError(); + return; + } else { + permissions = permissionAssigner.readPermissionsForGroup(name); + } + if (keys) { + templateRenderer.render(resolvePermissions(permissions)); + } else { + templateRenderer.render(resolvePermissionDescriptions(permissions)); + } + } + + private List resolvePermissions(Collection permissions) { + return permissions.stream().map(PermissionDescriptor::getValue).collect(Collectors.toList()); + } + + private List resolvePermissionDescriptions(Collection permissions) { + return permissions.stream() + .map(p -> descriptionResolver.getGlobalDescription(p.getValue()).orElse(p.getValue())) + .collect(Collectors.toList()); + } + + @VisibleForTesting + void setName(String name) { + this.name = name; + } + + @VisibleForTesting + void setKeys(boolean keys) { + this.keys = keys; + } +} + diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionRemoveCommand.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionRemoveCommand.java new file mode 100644 index 0000000000..55369325b0 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupPermissionRemoveCommand.java @@ -0,0 +1,93 @@ +/* + * 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.group.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; + +import javax.inject.Inject; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +@ParentCommand(value = GroupCommand.class) +@CommandLine.Command(name = "remove-permissions") +class GroupPermissionRemoveCommand implements Runnable { + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.group.name") + private String name; + + @CommandLine.Parameters(index = "1..", arity = "1..", paramLabel = "", descriptionKey = "scm.group.permissions") + private String[] removedPermissions; + + @CommandLine.Mixin + private final GroupTemplateRenderer templateRenderer; + private final PermissionAssigner permissionAssigner; + private final GroupManager groupManager; + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @Inject + GroupPermissionRemoveCommand(GroupTemplateRenderer templateRenderer, PermissionAssigner permissionAssigner, GroupManager groupManager) { + this.templateRenderer = templateRenderer; + this.permissionAssigner = permissionAssigner; + this.groupManager = groupManager; + } + + @Override + public void run() { + Group group = groupManager.get(name); + if (group == null) { + templateRenderer.renderNotFoundError(); + return; + } + Collection permissions = permissionAssigner.readPermissionsForGroup(name); + permissionAssigner.setPermissionsForGroup(name, getReducedPermissions(permissions)); + } + + private List getReducedPermissions(Collection permissions) { + return permissions.stream() + .filter(p -> Arrays.stream(removedPermissions) + .noneMatch(rp -> rp.equals(p.getValue())) + ) + .collect(Collectors.toList()); + } + + @VisibleForTesting + void setName(String name) { + this.name = name; + } + + @VisibleForTesting + void setRemovedPermissions(String[] removedPermissions) { + this.removedPermissions = removedPermissions; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupTemplateRenderer.java b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupTemplateRenderer.java index 0184ee096f..338cae742c 100644 --- a/scm-webapp/src/main/java/sonia/scm/group/cli/GroupTemplateRenderer.java +++ b/scm-webapp/src/main/java/sonia/scm/group/cli/GroupTemplateRenderer.java @@ -33,18 +33,29 @@ import sonia.scm.group.Group; import sonia.scm.template.TemplateEngineFactory; import javax.inject.Inject; +import java.util.Collection; import java.util.Collections; +import java.util.Map; + +import static java.util.Map.entry; class GroupTemplateRenderer extends TemplateRenderer { - private static final String NOT_FOUND_TEMPLATE = "{{i18n.groupNotFound}}"; + private static final String NOT_FOUND_TEMPLATE = "{{i18n.groupNotFound}}\n"; + private static final String UNKNOWN_PERMISSION_TEMPLATE = "{{i18n.permissionUnknown}}: {{permission}}\n"; private static final String DETAILS_TABLE_TEMPLATE = String.join("\n", "{{#rows}}", "{{#cols}}{{value}}{{/cols}}", "{{/rows}}" ); + private static final String PERMISSION_LIST_TEMPLATE = String.join("\n", + "{{#permissions}}", + "{{.}}", + "{{/permissions}}" + ); + private final GroupCommandBeanMapper mapper; @Inject @@ -72,4 +83,13 @@ class GroupTemplateRenderer extends TemplateRenderer { renderToStderr(NOT_FOUND_TEMPLATE, Collections.emptyMap()); getContext().exit(ExitCode.NOT_FOUND); } + + void renderUnknownPermissionError(String permission) { + renderToStderr(UNKNOWN_PERMISSION_TEMPLATE, Map.of("permission", permission)); + getContext().exit(ExitCode.USAGE); + } + + void render(Collection permissions) { + renderToStdout(PERMISSION_LIST_TEMPLATE, Map.ofEntries(entry("permissions", permissions))); + } } diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionAddCommand.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionAddCommand.java new file mode 100644 index 0000000000..386e0fbb4a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionAddCommand.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.user.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import javax.inject.Inject; +import java.util.Collection; + +@ParentCommand(value = UserCommand.class) +@CommandLine.Command(name = "add-permissions") +class UserPermissionAddCommand implements Runnable { + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.user.name") + private String name; + + @CommandLine.Parameters(index = "1..", arity = "1..", paramLabel = "", descriptionKey = "scm.user.permissions") + private String[] addedPermissions; + + @CommandLine.Mixin + private final UserTemplateRenderer templateRenderer; + private final PermissionAssigner permissionAssigner; + private final UserManager userManager; + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @Inject + UserPermissionAddCommand(UserTemplateRenderer templateRenderer, PermissionAssigner permissionAssigner, UserManager userManager) { + this.templateRenderer = templateRenderer; + this.permissionAssigner = permissionAssigner; + this.userManager = userManager; + } + + @Override + public void run() { + User user = userManager.get(name); + if (user == null) { + templateRenderer.renderNotFoundError(); + return; + } + Collection permissions = permissionAssigner.readPermissionsForUser(name); + for (String addedPermission : addedPermissions) { + if (isPermissionInvalid(addedPermission)) { + templateRenderer.renderUnknownPermissionError(addedPermission); + return; + } + permissions.add(new PermissionDescriptor(addedPermission)); + } + permissionAssigner.setPermissionsForUser(name, permissions); + } + + private boolean isPermissionInvalid(String permission) { + return permissionAssigner.getAvailablePermissions() + .stream() + .noneMatch(p -> p.getValue().equals(permission)); + } + + @VisibleForTesting + void setName(String name) { + this.name = name; + } + @VisibleForTesting + void setAddedPermissions(String[] addedPermissions) { + this.addedPermissions = addedPermissions; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionAvailableCommand.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionAvailableCommand.java new file mode 100644 index 0000000000..a9a4aa4753 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionAvailableCommand.java @@ -0,0 +1,73 @@ +/* + * 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.user.cli; + +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.cli.PermissionDescriptionResolver; +import sonia.scm.cli.Table; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; + +import javax.inject.Inject; +import java.util.Collection; +import java.util.Map; + +@ParentCommand(value = UserCommand.class) +@CommandLine.Command(name = "available-permissions") +class UserPermissionAvailableCommand implements Runnable { + + private static final String TABLE_TEMPLATE = String.join("\n", + "{{#rows}}", + "{{#cols}}{{#row.first}}{{#upper}}{{value}}{{/upper}}{{/row.first}}{{^row.first}}{{value}}{{/row.first}}{{^last}} {{/last}}{{/cols}}", + "{{/rows}}" + ); + + @CommandLine.Mixin + private final UserTemplateRenderer templateRenderer; + private final PermissionAssigner permissionAssigner; + private final PermissionDescriptionResolver descriptionResolver; + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @Inject + UserPermissionAvailableCommand(UserTemplateRenderer templateRenderer, PermissionAssigner permissionAssigner, PermissionDescriptionResolver descriptionResolver) { + this.templateRenderer = templateRenderer; + this.permissionAssigner = permissionAssigner; + this.descriptionResolver = descriptionResolver; + } + + @Override + public void run() { + Collection availablePermissions = permissionAssigner.getAvailablePermissions(); + Table table = templateRenderer.createTable(); + table.addHeader("value", "description"); + for (PermissionDescriptor descriptor : availablePermissions) { + String verb = descriptor.getValue(); + table.addRow(verb, descriptionResolver.getGlobalDescription(verb).orElse(verb)); + } + templateRenderer.renderToStdout(TABLE_TEMPLATE, Map.of("rows", table, "permissions", availablePermissions)); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionClearCommand.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionClearCommand.java new file mode 100644 index 0000000000..f6e855e4b2 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionClearCommand.java @@ -0,0 +1,72 @@ +/* + * 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.user.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import javax.inject.Inject; +import java.util.Collections; + +@ParentCommand(value = UserCommand.class) +@CommandLine.Command(name = "clear-permissions") +class UserPermissionClearCommand implements Runnable { + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.user.name") + private String name; + + @CommandLine.Mixin + private final UserTemplateRenderer templateRenderer; + private final PermissionAssigner permissionAssigner; + private final UserManager userManager; + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @Inject + UserPermissionClearCommand(UserTemplateRenderer templateRenderer, PermissionAssigner permissionAssigner, UserManager userManager) { + this.templateRenderer = templateRenderer; + this.permissionAssigner = permissionAssigner; + this.userManager = userManager; + } + + @Override + public void run() { + User user = userManager.get(name); + if (user == null) { + templateRenderer.renderNotFoundError(); + return; + } + permissionAssigner.setPermissionsForUser(name, Collections.emptyList()); + } + + @VisibleForTesting + void setName(String name) { + this.name = name; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionListCommand.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionListCommand.java new file mode 100644 index 0000000000..ac6ed425c6 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionListCommand.java @@ -0,0 +1,104 @@ +/* + * 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.user.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.cli.PermissionDescriptionResolver; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import javax.inject.Inject; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +@ParentCommand(value = UserCommand.class) +@CommandLine.Command(name = "list-permissions") +class UserPermissionListCommand implements Runnable { + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.user.username") + private String name; + + @CommandLine.Option(names = {"--keys", "-k"}, descriptionKey = "scm.user.list-permissions.keys") + private boolean keys; + + @CommandLine.Mixin + private final UserTemplateRenderer templateRenderer; + private final PermissionAssigner permissionAssigner; + private final PermissionDescriptionResolver descriptionResolver; + private final UserManager userManager; + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + + @Inject + UserPermissionListCommand(UserTemplateRenderer templateRenderer, PermissionAssigner permissionAssigner, PermissionDescriptionResolver descriptionResolver, UserManager userManager) { + this.templateRenderer = templateRenderer; + this.permissionAssigner = permissionAssigner; + this.descriptionResolver = descriptionResolver; + this.userManager = userManager; + } + + @Override + public void run() { + Collection permissions; + User user = userManager.get(name); + if (user == null) { + templateRenderer.renderNotFoundError(); + return; + } + permissions = permissionAssigner.readPermissionsForUser(name); + + if (keys) { + templateRenderer.render(resolvePermissions(permissions)); + } else { + templateRenderer.render(resolvePermissionDescriptions(permissions)); + } + } + + private List resolvePermissions(Collection permissions) { + return permissions.stream().map(PermissionDescriptor::getValue).collect(Collectors.toList()); + } + + private List resolvePermissionDescriptions(Collection permissions) { + return permissions.stream() + .map(p -> descriptionResolver.getGlobalDescription(p.getValue()).orElse(p.getValue())) + .collect(Collectors.toList()); + } + + @VisibleForTesting + void setName(String name) { + this.name = name; + } + + @VisibleForTesting + void setKeys(boolean keys) { + this.keys = keys; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionRemoveCommand.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionRemoveCommand.java new file mode 100644 index 0000000000..f57a300608 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserPermissionRemoveCommand.java @@ -0,0 +1,93 @@ +/* + * 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.user.cli; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import sonia.scm.cli.ParentCommand; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import javax.inject.Inject; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +@ParentCommand(value = UserCommand.class) +@CommandLine.Command(name = "remove-permissions") +class UserPermissionRemoveCommand implements Runnable { + + @CommandLine.Parameters(index = "0", paramLabel = "", descriptionKey = "scm.user.name") + private String name; + + @CommandLine.Parameters(index = "1..", arity = "1..", paramLabel = "", descriptionKey = "scm.user.permissions") + private String[] removedPermissions; + + @CommandLine.Mixin + private final UserTemplateRenderer templateRenderer; + private final PermissionAssigner permissionAssigner; + private final UserManager userManager; + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @Inject + UserPermissionRemoveCommand(UserTemplateRenderer templateRenderer, PermissionAssigner permissionAssigner, UserManager userManager) { + this.templateRenderer = templateRenderer; + this.permissionAssigner = permissionAssigner; + this.userManager = userManager; + } + + @Override + public void run() { + User user = userManager.get(name); + if (user == null) { + templateRenderer.renderNotFoundError(); + return; + } + Collection permissions = permissionAssigner.readPermissionsForUser(name); + permissionAssigner.setPermissionsForUser(name, getReducedPermissions(permissions)); + } + + private List getReducedPermissions(Collection permissions) { + return permissions.stream() + .filter(p -> Arrays.stream(removedPermissions) + .noneMatch(rp -> rp.equals(p.getValue())) + ) + .collect(Collectors.toList()); + } + + @VisibleForTesting + void setName(String name) { + this.name = name; + } + + @VisibleForTesting + void setRemovedPermissions(String[] removedPermissions) { + this.removedPermissions = removedPermissions; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/user/cli/UserTemplateRenderer.java b/scm-webapp/src/main/java/sonia/scm/user/cli/UserTemplateRenderer.java index bd23f325ea..3dd87cdd61 100644 --- a/scm-webapp/src/main/java/sonia/scm/user/cli/UserTemplateRenderer.java +++ b/scm-webapp/src/main/java/sonia/scm/user/cli/UserTemplateRenderer.java @@ -33,7 +33,11 @@ import sonia.scm.template.TemplateEngineFactory; import sonia.scm.user.User; import javax.inject.Inject; -import java.util.Collections; +import java.util.Collection; +import java.util.Map; + +import static java.util.Collections.emptyMap; +import static java.util.Map.entry; class UserTemplateRenderer extends TemplateRenderer { @@ -42,10 +46,19 @@ class UserTemplateRenderer extends TemplateRenderer { "{{#cols}}{{value}}{{/cols}}", "{{/rows}}" ); - private static final String PASSWORD_ERROR_TEMPLATE = "{{i18n.scmUserErrorPassword}}"; - private static final String EXTERNAL_ACTIVATE_TEMPLATE = "{{i18n.scmUserErrorExternalActivate}}"; - private static final String EXTERNAL_DEACTIVATE_TEMPLATE = "{{i18n.scmUserErrorExternalDeactivate}}"; - private static final String NOT_FOUND_TEMPLATE = "{{i18n.scmUserErrorNotFound}}"; + + private static final String PERMISSION_LIST_TEMPLATE = String.join("\n", + "{{#permissions}}", + "{{.}}", + "{{/permissions}}" + ); + + private static final String PASSWORD_ERROR_TEMPLATE = "{{i18n.scmUserErrorPassword}}\n"; + private static final String EXTERNAL_ACTIVATE_TEMPLATE = "{{i18n.scmUserErrorExternalActivate}}\n"; + private static final String EXTERNAL_DEACTIVATE_TEMPLATE = "{{i18n.scmUserErrorExternalDeactivate}}\n"; + private static final String NOT_FOUND_TEMPLATE = "{{i18n.scmUserErrorNotFound}}\n"; + private static final String UNKNOWN_PERMISSION_TEMPLATE = "{{i18n.permissionUnknown}}: {{permission}}\n"; + private final CliContext context; private final UserCommandBeanMapper mapper; @@ -74,26 +87,35 @@ class UserTemplateRenderer extends TemplateRenderer { } public void renderPasswordError() { - renderToStderr(PASSWORD_ERROR_TEMPLATE, Collections.emptyMap()); + renderToStderr(PASSWORD_ERROR_TEMPLATE, emptyMap()); context.getStderr().println(); context.exit(ExitCode.USAGE); } public void renderExternalActivateError() { - renderToStderr(EXTERNAL_ACTIVATE_TEMPLATE, Collections.emptyMap()); + renderToStderr(EXTERNAL_ACTIVATE_TEMPLATE, emptyMap()); context.getStderr().println(); context.exit(ExitCode.USAGE); } public void renderExternalDeactivateError() { - renderToStderr(EXTERNAL_DEACTIVATE_TEMPLATE, Collections.emptyMap()); + renderToStderr(EXTERNAL_DEACTIVATE_TEMPLATE, emptyMap()); context.getStderr().println(); context.exit(ExitCode.USAGE); } public void renderNotFoundError() { - renderToStderr(NOT_FOUND_TEMPLATE, Collections.emptyMap()); + renderToStderr(NOT_FOUND_TEMPLATE, emptyMap()); context.getStderr().println(); context.exit(ExitCode.NOT_FOUND); } + + void renderUnknownPermissionError(String permission) { + renderToStderr(UNKNOWN_PERMISSION_TEMPLATE, Map.of("permission", permission)); + getContext().exit(ExitCode.USAGE); + } + + public void render(Collection permissions) { + renderToStdout(PERMISSION_LIST_TEMPLATE, Map.ofEntries(entry("permissions", permissions))); + } } 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 8b31869b60..173576c761 100644 --- a/scm-webapp/src/main/resources/sonia/scm/cli/i18n.properties +++ b/scm-webapp/src/main/resources/sonia/scm/cli/i18n.properties @@ -30,6 +30,8 @@ transactionId = Transaction ID creationDate = Creation Date lastModified = Last Modified +value = Value +description = Description yes = yes no = no @@ -186,7 +188,24 @@ scm.user.convert-to-external.usage.description.0 = Convert user to external user ### Convert to internal user scm.user.convert-to-internal.usage.description.0 = Convert user to internal user on server -### Group +### List permissions for user +scm.user.list-permissions.usage.description.0 = List permissions from an existing user +scm.user.list-permissions.keys = Show permission values + +### Add permissions for user +scm.user.add-permissions.usage.description.0 = Add permissions for an existing user +scm.user.permissions = Permissions + +### Remove permissions from user +scm.user.remove-permissions.usage.description.0 = Remove permissions from an existing user + +### Clear permissions from user +scm.user.clear-permissions.usage.description.0 = Clear all permissions from an existing user + +### List available permissions for user +scm.user.available-permissions.usage.description.0 = List all available permissions for users + +## Group scm.group.name = Name scm.group.description = Description scm.group.members = Members @@ -229,12 +248,31 @@ scm.group.add-member.usage.description.0 = Add members to an existing group scm.group.add-member.name = Name of the group scm.group.add-member.members = Members to add to the group -### Remove members form group +### Remove members from group scm.group.remove-member.usage.description.0 = Remove members from an existing group scm.group.remove-member.name = Name of the group scm.group.remove-member.members = Members to remove from the group +### List permissions for group +scm.group.list-permissions.usage.description.0 = List permissions from an existing group +scm.group.list-permissions.keys = Show permission values + +### Add permissions for group +scm.group.add-permissions.usage.description.0 = Add permissions for an existing group +scm.group.permissions = Permissions + +### Remove permissions from group +scm.group.remove-permissions.usage.description.0 = Remove permissions from an existing group + +### Clear permissions from group +scm.group.clear-permissions.usage.description.0 = Clear all permissions from an existing group + +### List available permissions for group +scm.group.available-permissions.usage.description.0 = List all available permissions for groups + groupNotFound = Could not find group +permissionUnknown = Unknown permission + ## Plugin scm.plugin.usage.description.0 = Resource command for all plugin-management-related actions 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 a90d8cc7a5..f9a1b402e8 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 @@ -40,6 +40,8 @@ exception.missingSubCommand = Weiteres Kommando ben creationDate = Erstellt lastModified = Zuletzt bearbeitet +value = Wert +description = Beschreibung yes = ja no = nein @@ -196,7 +198,24 @@ scm.user.convert-to-external.usage.description.0 = Konvertiert einen Benutzer zu ### Convert to internal user scm.user.convert-to-internal.usage.description.0 = Konvertiert einen Benutzer zu einem internen Benutzer -### Group +### List permissions for user +scm.user.list-permissions.usage.description.0 = Listet die Berechtigungen eines Benutzers auf +scm.user.list-permissions.keys = Zeigt die Berechtigungswerte an + +### Add permissions for user +scm.user.add-permissions.usage.description.0 = Fügt einem Benutzer neue Berechtigungen hinzu +scm.user.permissions = Berechtigungen + +### Remove permissions from user +scm.user.remove-permissions.usage.description.0 = Entfernt die Berechtigungen eines Benutzers + +### Clear permissions from user +scm.user.clear-permissions.usage.description.0 = Entfernt alle Berechtigungen eines Benutzers + +### List available permissions for user +scm.user.available-permissions.usage.description.0 = Listet alle verfügbaren Benutzer Berechtigungen auf + +## Group scm.group.name = Name scm.group.description = Beschreibung scm.group.members = Mitglieder @@ -244,6 +263,24 @@ scm.group.remove-member.usage.description.0 = Entfernt Mitglieder von einer Grup scm.group.remove-member.name = Name der Gruppe scm.group.remove-member.members = Zu löschende Mitglieder +### List permissions for group +scm.group.list-permissions.usage.description.0 = Listet die Berechtigungen einer Gruppe auf +scm.group.list-permissions.keys = Zeigt die Berechtigungswerte an + +### Add permissions for group +scm.group.add-permissions.usage.description.0 = Fügt einer Gruppe neue Berechtigungen hinzu +scm.group.permissions = Berechtigungen + +### Remove permissions from group +scm.group.remove-permissions.usage.description.0 = Entfernt die Berechtigungen einer Gruppe + +### Clear permissions from group +scm.group.clear-permissions.usage.description.0 = Entfernt alle Berechtigungen einer Gruppe + +### List available permissions for group +scm.group.available-permissions.usage.description.0 = Listet alle verfügbaren Gruppen Berechtigungen auf + +permissionUnknown = Unbekannte Berechtigung groupNotFound = Gruppe konnte nicht gefunden werden ## Plugin diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupAddMemberCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupAddMemberCommandTest.java index ae4e529456..3dd41b75ca 100644 --- a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupAddMemberCommandTest.java +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupAddMemberCommandTest.java @@ -117,6 +117,6 @@ class GroupAddMemberCommandTest { assertThat(testRenderer.getStdOut()) .isEmpty(); assertThat(testRenderer.getStdErr()) - .isEqualTo("Could not find group"); + .isEqualTo("Could not find group\n"); } } diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupGetCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupGetCommandTest.java index 69cd39683b..66b928b5e9 100644 --- a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupGetCommandTest.java +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupGetCommandTest.java @@ -90,6 +90,6 @@ class GroupGetCommandTest { assertThat(testRenderer.getStdOut()) .isEmpty(); assertThat(testRenderer.getStdErr()) - .contains("Could not find group"); + .contains("Could not find group\n"); } } diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupModifyCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupModifyCommandTest.java index baab3461f1..0b9972274a 100644 --- a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupModifyCommandTest.java +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupModifyCommandTest.java @@ -175,6 +175,6 @@ class GroupModifyCommandTest { assertThat(testRenderer.getStdOut()) .isEmpty(); assertThat(testRenderer.getStdErr()) - .isEqualTo("Could not find group"); + .isEqualTo("Could not find group\n"); } } diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionAddCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionAddCommandTest.java new file mode 100644 index 0000000000..7fffa7d844 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionAddCommandTest.java @@ -0,0 +1,102 @@ +/* + * 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.group.cli; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.cli.CliExitException; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GroupPermissionAddCommandTest { + private final GroupTemplateTestRenderer testRenderer = new GroupTemplateTestRenderer(); + + @Mock + private GroupManager manager; + @Mock + private PermissionAssigner permissionAssigner; + + private GroupPermissionAddCommand command; + + @BeforeEach + void initCommand() { + command = new GroupPermissionAddCommand(testRenderer.getTemplateRenderer(), permissionAssigner, manager); + } + + @Test + void shouldRenderErrorForUnknownGroup() { + when(manager.get(any())).thenReturn(null); + command.setName("mygroup"); + command.setAddedPermissions(new String[]{"hitchhiker"}); + + assertThrows(CliExitException.class, () -> command.run()); + + assertThat(testRenderer.getStdErr()).contains("Could not find group"); + } + + @Test + void shouldRenderErrorForUnknownPermission() { + when(manager.get(any())).thenReturn(new Group()); + command.setName("mygroup"); + command.setAddedPermissions(new String[]{"hitchhiker"}); + + assertThrows(CliExitException.class, () -> command.run()); + + assertThat(testRenderer.getStdErr()).contains("Unknown permission: hitchhiker"); + } + + @Test + void shouldAddPermissions() { + when(manager.get(any())).thenReturn(new Group()); + command.setName("mygroup"); + command.setAddedPermissions(new String[]{"hitchhiker", "heartOfGold"}); + when(permissionAssigner.getAvailablePermissions()) + .thenReturn(List.of(new PermissionDescriptor("hitchhiker"), new PermissionDescriptor("heartOfGold"))); + + command.run(); + + verify(permissionAssigner).setPermissionsForGroup(eq("mygroup"), argThat(arg -> { + assertThat(arg.stream().map(PermissionDescriptor::getValue)).containsExactly("hitchhiker", "heartOfGold"); + return true; + })); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionAvailableCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionAvailableCommandTest.java new file mode 100644 index 0000000000..ad4ad47504 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionAvailableCommandTest.java @@ -0,0 +1,72 @@ +/* + * 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.group.cli; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.cli.PermissionDescriptionResolver; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GroupPermissionAvailableCommandTest { + private final GroupTemplateTestRenderer testRenderer = new GroupTemplateTestRenderer(); + + @Mock + private PermissionAssigner permissionAssigner; + @Mock + private PermissionDescriptionResolver descriptionResolver; + + private GroupPermissionAvailableCommand command; + + @BeforeEach + void initCommand() { + command = new GroupPermissionAvailableCommand(testRenderer.getTemplateRenderer(), permissionAssigner, descriptionResolver); + } + + @Test + void shouldRenderAvailablePermissions() { + when(permissionAssigner.getAvailablePermissions()) + .thenReturn(List.of(new PermissionDescriptor("hitchhiker"), new PermissionDescriptor("explorer"))); + when(descriptionResolver.getGlobalDescription("hitchhiker")).thenReturn(Optional.of("Hitchhikers Permission to the Galaxy")); + + command.run(); + + assertThat(testRenderer.getStdOut()).contains( + "VALUE DESCRIPTION", + "hitchhiker Hitchhikers Permission to the Galaxy", + "explorer explorer" + ); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionClearCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionClearCommandTest.java new file mode 100644 index 0000000000..59d0e681be --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionClearCommandTest.java @@ -0,0 +1,83 @@ +/* + * 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.group.cli; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.cli.CliExitException; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.security.PermissionAssigner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GroupPermissionClearCommandTest { + + private final GroupTemplateTestRenderer testRenderer = new GroupTemplateTestRenderer(); + + @Mock + private GroupManager manager; + @Mock + private PermissionAssigner permissionAssigner; + + private GroupPermissionClearCommand command; + + @BeforeEach + void initCommand() { + command = new GroupPermissionClearCommand(testRenderer.getTemplateRenderer(), permissionAssigner, manager); + } + + @Test + void shouldRenderErrorForUnknownUser() { + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + assertThat(testRenderer.getStdErr()).isEqualTo("Could not find group\n"); + } + + @Test + void shouldClearAllUserPermissions() { + when(manager.get(any())).thenReturn(new Group()); + command.setName("hitchhikers"); + + command.run(); + + verify(permissionAssigner).setPermissionsForGroup(eq("hitchhikers"), argThat(arg -> { + assertThat(arg).isEmpty(); + return true; + })); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionListCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionListCommandTest.java new file mode 100644 index 0000000000..b7c71058c2 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionListCommandTest.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.group.cli; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.cli.CliExitException; +import sonia.scm.cli.PermissionDescriptionResolver; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + + +@ExtendWith(MockitoExtension.class) +class GroupPermissionListCommandTest { + + private final GroupTemplateTestRenderer testRenderer = new GroupTemplateTestRenderer(); + + @Mock + private GroupManager manager; + @Mock + private PermissionAssigner permissionAssigner; + @Mock + private PermissionDescriptionResolver descriptionResolver; + + private GroupPermissionListCommand command; + + @BeforeEach + void initCommand() { + command = new GroupPermissionListCommand(testRenderer.getTemplateRenderer(), permissionAssigner, descriptionResolver, manager); + } + + @Test + void shouldRenderErrorForUnknownGroup() { + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + assertThat(testRenderer.getStdErr()).contains("Could not find group"); + } + + @Test + void shouldRenderPermissionDescription() { + when(manager.get(any())).thenReturn(new Group()); + command.setName("mygroup"); + when(permissionAssigner.readPermissionsForGroup("mygroup")).thenReturn(List.of(new PermissionDescriptor("hitchhiker"))); + when(descriptionResolver.getGlobalDescription("hitchhiker")).thenReturn(Optional.of("The Hitchhikers Permission to the Galaxy")); + + command.run(); + + assertThat(testRenderer.getStdOut()).contains("The Hitchhikers Permission to the Galaxy"); + } + + @Test + void shouldRenderPermissionKeys() { + when(manager.get(any())).thenReturn(new Group()); + command.setName("mygroup"); + command.setKeys(true); + when(permissionAssigner.readPermissionsForGroup("mygroup")).thenReturn(List.of(new PermissionDescriptor("hitchhiker"))); + + command.run(); + + assertThat(testRenderer.getStdOut()).isEqualTo("hitchhiker\n"); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionRemoveCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionRemoveCommandTest.java new file mode 100644 index 0000000000..7c7775896b --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupPermissionRemoveCommandTest.java @@ -0,0 +1,91 @@ +/* + * 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.group.cli; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.cli.CliExitException; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GroupPermissionRemoveCommandTest { + + private final GroupTemplateTestRenderer testRenderer = new GroupTemplateTestRenderer(); + + @Mock + private GroupManager manager; + @Mock + private PermissionAssigner permissionAssigner; + + private GroupPermissionRemoveCommand command; + + @BeforeEach + void initCommand() { + command = new GroupPermissionRemoveCommand(testRenderer.getTemplateRenderer(), permissionAssigner, manager); + } + + @Test + void shouldRenderErrorForUnknownUser() { + when(manager.get(any())).thenReturn(null); + command.setName("mygroup"); + command.setRemovedPermissions(new String[]{"hitchhiker"}); + + assertThrows(CliExitException.class, () -> command.run()); + + assertThat(testRenderer.getStdErr()).contains("Could not find group"); + } + + @Test + void shouldRemovePermissions() { + when(manager.get(any())).thenReturn(new Group()); + command.setName("mygroup"); + command.setRemovedPermissions(new String[]{"hitchhiker", "heartOfGold"}); + when(permissionAssigner.readPermissionsForGroup("mygroup")) + .thenReturn(List.of(new PermissionDescriptor("hitchhiker"), new PermissionDescriptor("heartOfGold"), new PermissionDescriptor("puzzle42"))); + + command.run(); + + verify(permissionAssigner).setPermissionsForGroup(eq("mygroup"), argThat(arg -> { + assertThat(arg.stream().map(PermissionDescriptor::getValue)).containsExactly("puzzle42"); + return true; + })); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupRemoveMemberCommandTest.java b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupRemoveMemberCommandTest.java index 9a5e85a470..fbd1364715 100644 --- a/scm-webapp/src/test/java/sonia/scm/group/cli/GroupRemoveMemberCommandTest.java +++ b/scm-webapp/src/test/java/sonia/scm/group/cli/GroupRemoveMemberCommandTest.java @@ -117,6 +117,6 @@ class GroupRemoveMemberCommandTest { assertThat(testRenderer.getStdOut()) .isEmpty(); assertThat(testRenderer.getStdErr()) - .isEqualTo("Could not find group"); + .isEqualTo("Could not find group\n"); } } diff --git a/scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionAddCommandTest.java b/scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionAddCommandTest.java new file mode 100644 index 0000000000..cf5b113d26 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionAddCommandTest.java @@ -0,0 +1,102 @@ +/* + * 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.user.cli; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.cli.CliExitException; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserPermissionAddCommandTest { + + private final UserTemplateTestRenderer testRenderer = new UserTemplateTestRenderer(); + + @Mock + private UserManager manager; + @Mock + private PermissionAssigner permissionAssigner; + + private UserPermissionAddCommand command; + + @BeforeEach + void initCommand() { + command = new UserPermissionAddCommand(testRenderer.getTemplateRenderer(), permissionAssigner, manager); + } + + @Test + void shouldRenderErrorForUnknownUser() { + when(manager.get(any())).thenReturn(null); + command.setName("trillian"); + command.setAddedPermissions(new String[]{"hitchhiker"}); + + assertThrows(CliExitException.class, () -> command.run()); + + assertThat(testRenderer.getStdErr()).contains("Could not find user"); + } + + @Test + void shouldRenderErrorForUnknownPermission() { + when(manager.get(any())).thenReturn(new User()); + command.setName("trillian"); + command.setAddedPermissions(new String[]{"hitchhiker"}); + + assertThrows(CliExitException.class, () -> command.run()); + + assertThat(testRenderer.getStdErr()).contains("Unknown permission: hitchhiker"); + } + + @Test + void shouldAddPermissions() { + when(manager.get(any())).thenReturn(new User()); + command.setName("trillian"); + command.setAddedPermissions(new String[]{"hitchhiker", "heartOfGold"}); + when(permissionAssigner.getAvailablePermissions()) + .thenReturn(List.of(new PermissionDescriptor("hitchhiker"), new PermissionDescriptor("heartOfGold"))); + + command.run(); + + verify(permissionAssigner).setPermissionsForUser(eq("trillian"), argThat(arg -> { + assertThat(arg.stream().map(PermissionDescriptor::getValue)).containsExactly("hitchhiker", "heartOfGold"); + return true; + })); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionAvailableCommandTest.java b/scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionAvailableCommandTest.java new file mode 100644 index 0000000000..ec698695b0 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionAvailableCommandTest.java @@ -0,0 +1,73 @@ +/* + * 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.user.cli; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.cli.PermissionDescriptionResolver; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserPermissionAvailableCommandTest { + + private final UserTemplateTestRenderer testRenderer = new UserTemplateTestRenderer(); + + @Mock + private PermissionAssigner permissionAssigner; + @Mock + private PermissionDescriptionResolver descriptionResolver; + + private UserPermissionAvailableCommand command; + + @BeforeEach + void initCommand() { + command = new UserPermissionAvailableCommand(testRenderer.getTemplateRenderer(), permissionAssigner, descriptionResolver); + } + + @Test + void shouldRenderAvailablePermissions() { + when(permissionAssigner.getAvailablePermissions()) + .thenReturn(List.of(new PermissionDescriptor("hitchhiker"), new PermissionDescriptor("explorer"))); + when(descriptionResolver.getGlobalDescription("hitchhiker")).thenReturn(Optional.of("Hitchhikers Permission to the Galaxy")); + + command.run(); + + assertThat(testRenderer.getStdOut()).contains( + "VALUE DESCRIPTION", + "hitchhiker Hitchhikers Permission to the Galaxy", + "explorer explorer" + ); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionClearCommandTest.java b/scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionClearCommandTest.java new file mode 100644 index 0000000000..a700cee14b --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionClearCommandTest.java @@ -0,0 +1,83 @@ +/* + * 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.user.cli; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.cli.CliExitException; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserPermissionClearCommandTest { + + private final UserTemplateTestRenderer testRenderer = new UserTemplateTestRenderer(); + + @Mock + private UserManager manager; + @Mock + private PermissionAssigner permissionAssigner; + + private UserPermissionClearCommand command; + + @BeforeEach + void initCommand() { + command = new UserPermissionClearCommand(testRenderer.getTemplateRenderer(), permissionAssigner, manager); + } + + @Test + void shouldRenderErrorForUnknownUser() { + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + assertThat(testRenderer.getStdErr()).isEqualTo("Could not find user\n"); + } + + @Test + void shouldClearAllUserPermissions() { + when(manager.get(any())).thenReturn(new User()); + command.setName("trillian"); + + command.run(); + + verify(permissionAssigner).setPermissionsForUser(eq("trillian"), argThat(arg -> { + assertThat(arg).isEmpty(); + return true; + })); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionListCommandTest.java b/scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionListCommandTest.java new file mode 100644 index 0000000000..e17b8e07ec --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionListCommandTest.java @@ -0,0 +1,98 @@ +/* + * 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.user.cli; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.cli.CliExitException; +import sonia.scm.cli.PermissionDescriptionResolver; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserPermissionListCommandTest { + + private final UserTemplateTestRenderer testRenderer = new UserTemplateTestRenderer(); + + @Mock + private UserManager manager; + @Mock + private PermissionAssigner permissionAssigner; + @Mock + private PermissionDescriptionResolver descriptionResolver; + + private UserPermissionListCommand command; + + @BeforeEach + void initCommand() { + command = new UserPermissionListCommand(testRenderer.getTemplateRenderer(), permissionAssigner, descriptionResolver, manager); + } + + @Test + void shouldRenderErrorForUnknownGroup() { + when(manager.get(any())).thenReturn(null); + + assertThrows(CliExitException.class, () -> command.run()); + + assertThat(testRenderer.getStdErr()).contains("Could not find user"); + } + + @Test + void shouldRenderPermissionDescription() { + when(manager.get(any())).thenReturn(new User()); + command.setName("trillian"); + when(permissionAssigner.readPermissionsForUser("trillian")).thenReturn(List.of(new PermissionDescriptor("hitchhiker"))); + when(descriptionResolver.getGlobalDescription("hitchhiker")).thenReturn(Optional.of("The Hitchhikers Permission to the Galaxy")); + + command.run(); + + assertThat(testRenderer.getStdOut()).contains("The Hitchhikers Permission to the Galaxy"); + } + + @Test + void shouldRenderPermissionKeys() { + when(manager.get(any())).thenReturn(new User()); + command.setName("trillian"); + command.setKeys(true); + when(permissionAssigner.readPermissionsForUser("trillian")).thenReturn(List.of(new PermissionDescriptor("hitchhiker"))); + + command.run(); + + assertThat(testRenderer.getStdOut()).isEqualTo("hitchhiker\n"); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionRemoveCommandTest.java b/scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionRemoveCommandTest.java new file mode 100644 index 0000000000..fd0f8fc2ef --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/user/cli/UserPermissionRemoveCommandTest.java @@ -0,0 +1,91 @@ +/* + * 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.user.cli; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.cli.CliExitException; +import sonia.scm.security.PermissionAssigner; +import sonia.scm.security.PermissionDescriptor; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserPermissionRemoveCommandTest { + + private final UserTemplateTestRenderer testRenderer = new UserTemplateTestRenderer(); + + @Mock + private UserManager manager; + @Mock + private PermissionAssigner permissionAssigner; + + private UserPermissionRemoveCommand command; + + @BeforeEach + void initCommand() { + command = new UserPermissionRemoveCommand(testRenderer.getTemplateRenderer(), permissionAssigner, manager); + } + + @Test + void shouldRenderErrorForUnknownUser() { + when(manager.get(any())).thenReturn(null); + command.setName("trillian"); + command.setRemovedPermissions(new String[]{"hitchhiker"}); + + assertThrows(CliExitException.class, () -> command.run()); + + assertThat(testRenderer.getStdErr()).contains("Could not find user"); + } + + @Test + void shouldRemovePermissions() { + when(manager.get(any())).thenReturn(new User()); + command.setName("trillian"); + command.setRemovedPermissions(new String[]{"hitchhiker", "puzzle42"}); + when(permissionAssigner.readPermissionsForUser("trillian")) + .thenReturn(List.of(new PermissionDescriptor("hitchhiker"), new PermissionDescriptor("heartOfGold"), new PermissionDescriptor("puzzle42"))); + + command.run(); + + verify(permissionAssigner).setPermissionsForUser(eq("trillian"), argThat(arg -> { + assertThat(arg.stream().map(PermissionDescriptor::getValue)).containsExactly("heartOfGold"); + return true; + })); + } +}