From 2f4209785e5141a3ce140be4363b14607a2ef709 Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Tue, 8 Feb 2022 10:26:17 +0000 Subject: [PATCH] StringInput masking - Add a feature to define a mask character for StringInput which help when there's a need to as something sensitive. - This masks both input and result value. --- .../shell/component/StringInput.java | 95 ++++++++++++++++++- .../shell/component/string-input-default.stg | 16 +++- .../shell/component/StringInputTests.java | 26 +++++ .../samples/standard/ComponentCommands.java | 5 +- 4 files changed, 132 insertions(+), 10 deletions(-) diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/StringInput.java b/spring-shell-core/src/main/java/org/springframework/shell/component/StringInput.java index 5271fbf0..d9512af7 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/StringInput.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/StringInput.java @@ -40,6 +40,7 @@ public class StringInput extends AbstractTextComponent context) { if (context != null && currentContext == context) { return currentContext; } - currentContext = StringInputContext.of(defaultValue); + currentContext = StringInputContext.of(defaultValue, maskCharacter); currentContext.setName(getName()); context.stream().forEach(e -> { currentContext.put(e.getKey(), e.getValue()); @@ -123,13 +133,48 @@ public class StringInput extends AbstractTextComponent toTemplateModel() { Map attributes = super.toTemplateModel(); attributes.put("defaultValue", getDefaultValue() != null ? getDefaultValue() : null); + attributes.put("maskedInput", getMaskedInput()); + attributes.put("maskedResultValue", getMaskedResultValue()); + attributes.put("maskCharacter", getMaskCharacter()); + attributes.put("hasMaskCharacter", hasMaskCharacter()); Map model = new HashMap<>(); model.put("model", attributes); return model; } + + private String maybeMask(String str) { + if (StringUtils.hasLength(str) && maskCharacter != null) { + return new String(new char[str.length()]).replace('\0', maskCharacter); + } + else { + return str; + } + } } private class DefaultRenderer implements Function> { diff --git a/spring-shell-core/src/main/resources/org/springframework/shell/component/string-input-default.stg b/spring-shell-core/src/main/resources/org/springframework/shell/component/string-input-default.stg index be71d74c..b451fa9e 100644 --- a/spring-shell-core/src/main/resources/org/springframework/shell/component/string-input-default.stg +++ b/spring-shell-core/src/main/resources/org/springframework/shell/component/string-input-default.stg @@ -1,9 +1,17 @@ // info section after '? xxx' info(model) ::= <% - - + + + + + <("[Default "); format="value"><("]"); format="value"> + -<("[Default "); format="value"><("]"); format="value"> + + + + <("[Default "); format="value"><("]"); format="value"> + %> @@ -14,7 +22,7 @@ question_name(model) ::= << // component result result(model) ::= << - + >> // component is running diff --git a/spring-shell-core/src/test/java/org/springframework/shell/component/StringInputTests.java b/spring-shell-core/src/test/java/org/springframework/shell/component/StringInputTests.java index 0cffc0a8..06b2d708 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/component/StringInputTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/component/StringInputTests.java @@ -85,6 +85,32 @@ public class StringInputTests extends AbstractShellTests { assertThat(consoleOut()).contains("component1 component1ResultValue"); } + @Test + public void testResultBasicWithMask() throws InterruptedException { + ComponentContext empty = ComponentContext.empty(); + StringInput component1 = new StringInput(getTerminal(), "component1", "component1ResultValue"); + component1.setPrintResults(true); + component1.setMaskCharater('*'); + component1.setResourceLoader(new DefaultResourceLoader()); + component1.setTemplateExecutor(getTemplateExecutor()); + + service.execute(() -> { + StringInputContext run1Context = component1.run(empty); + result1.set(run1Context); + latch1.countDown(); + }); + + TestBuffer testBuffer = new TestBuffer().cr(); + write(testBuffer.getBytes()); + + latch1.await(2, TimeUnit.SECONDS); + StringInputContext run1Context = result1.get(); + + assertThat(run1Context).isNotNull(); + assertThat(run1Context.getResultValue()).isEqualTo("component1ResultValue"); + assertThat(consoleOut()).contains("component1 *********************"); + } + @Test public void testResultUserInput() throws InterruptedException { ComponentContext empty = ComponentContext.empty(); diff --git a/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentCommands.java b/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentCommands.java index a70a8b94..01299e30 100644 --- a/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentCommands.java +++ b/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentCommands.java @@ -51,10 +51,13 @@ public class ComponentCommands extends AbstractShellComponent implements Resourc } @ShellMethod(key = "component string", value = "String input", group = "Components") - public String stringInput() { + public String stringInput(boolean mask) { StringInput component = new StringInput(getTerminal(), "Enter value", "myvalue"); component.setResourceLoader(resourceLoader); component.setTemplateExecutor(templateExecutor); + if (mask) { + component.setMaskCharater('*'); + } StringInputContext context = component.run(StringInputContext.empty()); return "Got value " + context.getResultValue(); }