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.
This commit is contained in:
@@ -40,6 +40,7 @@ public class StringInput extends AbstractTextComponent<String, StringInputContex
|
||||
|
||||
private final String defaultValue;
|
||||
private StringInputContext currentContext;
|
||||
private Character maskCharacter;
|
||||
|
||||
public StringInput(Terminal terminal) {
|
||||
this(terminal, null, null, null);
|
||||
@@ -57,12 +58,21 @@ public class StringInput extends AbstractTextComponent<String, StringInputContex
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a mask character for input and result value.
|
||||
*
|
||||
* @param maskCharacter a mask character
|
||||
*/
|
||||
public void setMaskCharater(Character maskCharacter) {
|
||||
this.maskCharacter = maskCharacter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StringInputContext getThisContext(ComponentContext<?> 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<String, StringInputContex
|
||||
*/
|
||||
void setDefaultValue(String defaultValue);
|
||||
|
||||
/**
|
||||
* Sets a mask character.
|
||||
*
|
||||
* @param maskCharacter the mask character
|
||||
*/
|
||||
void setMaskCharacter(Character maskCharacter);
|
||||
|
||||
/**
|
||||
* Gets a masked input.
|
||||
*
|
||||
* @return a masked input
|
||||
*/
|
||||
String getMaskedInput();
|
||||
|
||||
/**
|
||||
* Gets a masked result value.
|
||||
*
|
||||
* @return masked result value
|
||||
*/
|
||||
String getMaskedResultValue();
|
||||
|
||||
/**
|
||||
* Returns flag if there is a mask character defined.
|
||||
*
|
||||
* @return true if mask character defined, false otherwise
|
||||
*/
|
||||
boolean hasMaskCharacter();
|
||||
|
||||
/**
|
||||
* Gets a mask character.
|
||||
*
|
||||
* @return a mask character.
|
||||
*/
|
||||
Character getMaskCharacter();
|
||||
|
||||
/**
|
||||
* Gets an empty {@link StringInputContext}.
|
||||
*
|
||||
* @return empty path input context
|
||||
*/
|
||||
public static StringInputContext empty() {
|
||||
return of(null);
|
||||
return of(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,8 +182,8 @@ public class StringInput extends AbstractTextComponent<String, StringInputContex
|
||||
*
|
||||
* @return path input context
|
||||
*/
|
||||
public static StringInputContext of(String defaultValue) {
|
||||
return new DefaultStringInputContext(defaultValue);
|
||||
public static StringInputContext of(String defaultValue, Character maskCharacter) {
|
||||
return new DefaultStringInputContext(defaultValue, maskCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,9 +191,11 @@ public class StringInput extends AbstractTextComponent<String, StringInputContex
|
||||
implements StringInputContext {
|
||||
|
||||
private String defaultValue;
|
||||
private Character maskCharacter;
|
||||
|
||||
public DefaultStringInputContext(String defaultValue) {
|
||||
public DefaultStringInputContext(String defaultValue, Character maskCharacter) {
|
||||
this.defaultValue = defaultValue;
|
||||
this.maskCharacter = maskCharacter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -161,14 +208,52 @@ public class StringInput extends AbstractTextComponent<String, StringInputContex
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaskCharacter(Character maskCharacter) {
|
||||
this.maskCharacter = maskCharacter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMaskedInput() {
|
||||
return maybeMask(getInput());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMaskedResultValue() {
|
||||
return maybeMask(getResultValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMaskCharacter() {
|
||||
return maskCharacter != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Character getMaskCharacter() {
|
||||
return maskCharacter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> toTemplateModel() {
|
||||
Map<String, Object> 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<String, Object> 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<StringInputContext, List<AttributedString>> {
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
// info section after '? xxx'
|
||||
info(model) ::= <%
|
||||
<if(model.input)>
|
||||
<model.input>
|
||||
<if(model.hasMaskCharacter)>
|
||||
<if(model.maskedInput)>
|
||||
<model.maskedInput>
|
||||
<else>
|
||||
<("[Default "); format="value"><model.defaultValue; format="value"><("]"); format="value">
|
||||
<endif>
|
||||
<else>
|
||||
<("[Default "); format="value"><model.defaultValue; format="value"><("]"); format="value">
|
||||
<if(model.input)>
|
||||
<model.input>
|
||||
<else>
|
||||
<("[Default "); format="value"><model.defaultValue; format="value"><("]"); format="value">
|
||||
<endif>
|
||||
<endif>
|
||||
%>
|
||||
|
||||
@@ -14,7 +22,7 @@ question_name(model) ::= <<
|
||||
|
||||
// component result
|
||||
result(model) ::= <<
|
||||
<question_name(model)> <model.resultValue; format="value">
|
||||
<question_name(model)> <model.maskedResultValue; format="value">
|
||||
>>
|
||||
|
||||
// component is running
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user