diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java index 1ed99e7d1b..a476eeaf4e 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java @@ -44,6 +44,9 @@ private FormConstants() { /** The resource type for email input v1 */ public static final String RT_FD_FORM_EMAIL_V1 = RT_FD_FORM_PREFIX + "emailinput/v1/emailinput"; + /** The resource type for password input v1 */ + public static final String RT_FD_FORM_PASSWORD_V1 = RT_FD_FORM_PREFIX + "passwordinput/v1/passwordinput"; + /** The resource type for button v1 */ public static final String RT_FD_FORM_BUTTON_V1 = RT_FD_FORM_PREFIX + "button/v1/button"; diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java index c8b8563b79..6e985cf930 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java @@ -152,6 +152,7 @@ private ReservedProperties() { public static final String PN_SHOW_AS_POPUP = "showAsPopup"; public static final String PN_TEXT_IS_RICH = "textIsRich"; public static final String PN_MULTILINE = "multiLine"; + public static final String PN_SHOW_HIDE_PASSWORD = "showHidePassword"; public static final String PN_DESIGN_DEFAULT_TYPE = Title.PN_DESIGN_DEFAULT_TYPE; public static final String PN_TITLE_LINK_DISABLED = Title.PN_TITLE_LINK_DISABLED; public static final String PN_DRAG_DROP_TEXT = "dragDropText"; diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java index 150f6bc9fc..fca69a22c2 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java @@ -40,7 +40,8 @@ @Model( adaptables = { SlingHttpServletRequest.class, Resource.class }, adapters = { TextInput.class, ComponentExporter.class }, - resourceType = { FormConstants.RT_FD_FORM_TEXT_V1, FormConstants.RT_FD_FORM_EMAIL_V1, FormConstants.RT_FD_FORM_TELEPHONE_V1 }) + resourceType = { FormConstants.RT_FD_FORM_TEXT_V1, FormConstants.RT_FD_FORM_EMAIL_V1, FormConstants.RT_FD_FORM_TELEPHONE_V1, + FormConstants.RT_FD_FORM_PASSWORD_V1 }) @Exporter( name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION) @@ -65,6 +66,10 @@ public class TextInputImpl extends AbstractFieldImpl implements TextInput { @Nullable protected String autocomplete; + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_SHOW_HIDE_PASSWORD) + @Default(booleanValues = true) + protected boolean showHidePassword; + /** Type number specific constraints **/ private Object exclusiveMinimumVaue; private Object exclusiveMaximumValue; @@ -83,6 +88,17 @@ public String getFieldType() { return super.getFieldType(FieldType.TEXT_INPUT); } + @Override + @Nullable + public Object[] getDefault() { + // password values must never be exposed in rendered markup or the exported JSON model, + // regardless of how the underlying property was set (authoring dialog, direct content write, etc.) + if (FieldType.PASSWORD.getValue().equals(getFieldType())) { + return null; + } + return super.getDefault(); + } + @Override @Nullable public Integer getMinLength() { @@ -106,6 +122,11 @@ public String getAutoComplete() { return autocomplete; } + @Override + public boolean isShowHidePasswordEnabled() { + return showHidePassword; + } + @Override @Nullable public Long getMinimum() { diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java index 4415ceabde..0a5c0dd316 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java @@ -43,6 +43,18 @@ default boolean isMultiLine() { return false; } + /** + * Returns {@code true} if the password show/hide visibility toggle should be rendered, otherwise {@code false}. + * Only relevant when the field's {@code fieldType} is {@code password}. + * + * @return {@code true} if the visibility toggle should be rendered, otherwise {@code false} + * @since com.adobe.cq.forms.core.components.models.form 2.0.0 + */ + @JsonIgnore + default boolean isShowHidePasswordEnabled() { + return true; + } + /** * Returns {@code "off"} if autocomplete if disabled, otherwise {@code "on"} or values listed @see * here diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java index 1542522422..21ca762ac9 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java @@ -72,6 +72,8 @@ public class TextInputImplTest { private static final String PATH_TEXTINPUT_EMPTYVALUE_EMPTY_STRING = CONTENT_ROOT + "/textinput-emptyvalue-empty-string"; private static final String PATH_TEXTINPUT_EMPTYVALUE_INVALID = CONTENT_ROOT + "/textinput-emptyvalue-invalid"; private static final String PATH_TEXTINPUT_EMPTYVALUE_NOT_SET = CONTENT_ROOT + "/textinput-emptyvalue-not-set"; + private static final String PATH_PASSWORD_TEXTINPUT = CONTENT_ROOT + "/password-textinput"; + private static final String PATH_PASSWORD_TEXTINPUT_TOGGLE_DISABLED = CONTENT_ROOT + "/password-textinput-toggle-disabled"; private final AemContext context = FormsCoreComponentTestContext.newAemContext(); @@ -594,6 +596,34 @@ void testEmptyValueEnumGetValue() { assertEquals("", AbstractFieldImpl.EmptyValue.EMPTY_STRING.getValue()); } + @Test + void testFieldTypeForPassword() { + TextInput textInput = Utils.getComponentUnderTest(PATH_PASSWORD_TEXTINPUT, TextInput.class, context); + assertEquals(FieldType.PASSWORD.getValue(), textInput.getFieldType()); + assertEquals(FormConstants.RT_FD_FORM_PASSWORD_V1, textInput.getExportedType()); + } + + @Test + void testShowHidePasswordEnabledByDefault() { + TextInput textInput = Utils.getComponentUnderTest(PATH_PASSWORD_TEXTINPUT, TextInput.class, context); + assertTrue(textInput.isShowHidePasswordEnabled()); + TextInput textInputMock = Mockito.mock(TextInput.class); + Mockito.when(textInputMock.isShowHidePasswordEnabled()).thenCallRealMethod(); + assertTrue(textInputMock.isShowHidePasswordEnabled()); + } + + @Test + void testShowHidePasswordCanBeDisabled() { + TextInput textInput = Utils.getComponentUnderTest(PATH_PASSWORD_TEXTINPUT_TOGGLE_DISABLED, TextInput.class, context); + assertFalse(textInput.isShowHidePasswordEnabled()); + } + + @Test + void testJSONExportForPassword() throws Exception { + TextInput textInput = Utils.getComponentUnderTest(PATH_PASSWORD_TEXTINPUT, TextInput.class, context); + Utils.testJSONExport(textInput, Utils.getTestExporterJSONPath(BASE, PATH_PASSWORD_TEXTINPUT)); + } + @AfterEach void tearDown() { System.clearProperty(FeatureToggleConstants.FT_SKIP_DEFAULT_SET_PROPERTY_EVENT); diff --git a/bundles/af-core/src/test/resources/form/textinput/exporter-password-textinput.json b/bundles/af-core/src/test/resources/form/textinput/exporter-password-textinput.json new file mode 100644 index 0000000000..6763c23627 --- /dev/null +++ b/bundles/af-core/src/test/resources/form/textinput/exporter-password-textinput.json @@ -0,0 +1,20 @@ +{ + "id": "passwordinput-5c6da9d601", + "fieldType": "password", + "name": "abc", + "type": "string", + "minLength": 8, + "maxLength": 20, + "label": { + "value": "def" + }, + "properties": { + "fd:path": "/content/password-textinput" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + ":type": "core/fd/components/form/passwordinput/v1/passwordinput" +} diff --git a/bundles/af-core/src/test/resources/form/textinput/test-content.json b/bundles/af-core/src/test/resources/form/textinput/test-content.json index 9fad69379c..54df55b2b2 100644 --- a/bundles/af-core/src/test/resources/form/textinput/test-content.json +++ b/bundles/af-core/src/test/resources/form/textinput/test-content.json @@ -234,5 +234,22 @@ "sling:resourceType" : "core/fd/components/form/textinput/v1/textinput", "name" : "abc", "jcr:title" : "def" + }, + "password-textinput" : { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType" : "core/fd/components/form/passwordinput/v1/passwordinput", + "name" : "abc", + "jcr:title" : "def", + "fieldType": "password", + "minLength": 8, + "maxLength": 20 + }, + "password-textinput-toggle-disabled" : { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType" : "core/fd/components/form/passwordinput/v1/passwordinput", + "name" : "abc", + "jcr:title" : "def", + "fieldType": "password", + "showHidePassword": false } } diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/passwordinput/.content.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/passwordinput/.content.xml new file mode 100644 index 0000000000..4596ce87d2 --- /dev/null +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/passwordinput/.content.xml @@ -0,0 +1,7 @@ + + diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/passwordinput/_cq_template.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/passwordinput/_cq_template.xml new file mode 100644 index 0000000000..a07d742033 --- /dev/null +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/passwordinput/_cq_template.xml @@ -0,0 +1,6 @@ + + diff --git a/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml index c381b90879..73fb2442c9 100644 --- a/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml +++ b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml @@ -28,6 +28,7 @@ + diff --git a/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/passwordinput/.content.xml b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/passwordinput/.content.xml new file mode 100644 index 0000000000..042b98dde5 --- /dev/null +++ b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/passwordinput/.content.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/passwordinput/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/passwordinput/.content.xml new file mode 100644 index 0000000000..7256712059 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/passwordinput/.content.xml @@ -0,0 +1,7 @@ + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/passwordinput/basic/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/passwordinput/basic/.content.xml new file mode 100644 index 0000000000..86a116e0b4 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/passwordinput/basic/.content.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/passwordinput/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/passwordinput/.content.xml new file mode 100644 index 0000000000..bbf62ed22c --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/passwordinput/.content.xml @@ -0,0 +1,5 @@ + +