You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by dk...@apache.org on 2021/01/15 15:07:28 UTC

[sling-org-apache-sling-app-cms] branch forms-enhancements created (now c6e02d9)

This is an automated email from the ASF dual-hosted git repository.

dklco pushed a change to branch forms-enhancements
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-app-cms.git.


      at c6e02d9  Working on enhanced capabilities for the reference forms

This branch includes the following new commits:

     new c6e02d9  Working on enhanced capabilities for the reference forms

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[sling-org-apache-sling-app-cms] 01/01: Working on enhanced capabilities for the reference forms

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch forms-enhancements
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-app-cms.git

commit c6e02d9be734f5f2433f6e69e85b885333d5836d
Author: Dan Klco <dk...@apache.org>
AuthorDate: Fri Jan 15 10:07:11 2021 -0500

    Working on enhanced capabilities for the reference forms
---
 reference/pom.xml                                  |  20 +++
 .../sling/cms/reference/forms/FormConstants.java   |  37 +++++
 .../sling/cms/reference/forms/FormUtils.java       |  28 ++++
 .../cms/reference/forms/impl/FormHandler.java      |  15 +-
 .../forms/impl/actions/CreateUserAction.java       | 163 +++++++++++++++++++
 .../impl/actions/RequestPasswordResetAction.java   | 115 ++++++++++++++
 .../forms/impl/actions/ResetPasswordAction.java    | 126 +++++++++++++++
 .../forms/impl/actions/SendEmailAction.java        |  31 +++-
 .../forms/impl/actions/UpdateProfileAction.java    |  83 +++++-----
 .../forms/impl/actions/UpdateResourceAction.java   | 113 +++++++++++++
 .../providers/RequestParametersValueProvider.java  |  56 +++++++
 .../providers/SuffixResourceFormValueProvider.java |  63 ++++++++
 .../providers/UserProfileFormValueProvider.java    |   3 +-
 .../main/resources/OSGI-INF/l10n/bundle.properties |  24 ++-
 .../components/forms/actions/createuser.json       |   5 +
 .../forms/actions/createuser/createuser.jsp        |  32 ++++
 .../components/forms/actions/createuser/edit.json  |  47 ++++++
 .../forms/actions/requestpasswordreset/edit.json   |  16 ++
 .../requestpasswordreset/requestpasswordreset.jsp  |  26 +++
 .../components/forms/actions/resetpassword.json    |   5 +
 .../forms/actions/resetpassword/edit.json          |   5 +
 .../forms/actions/resetpassword/resetpassword.jsp  |  22 +++
 .../components/forms/actions/updateresource.json   |   5 +
 .../forms/actions/updateresource/edit.json         |  32 ++++
 .../actions/updateresource/updateresource.jsp      |  30 ++++
 .../components/forms/fields/textarea/textarea.jsp  |   4 +-
 .../forms/fields/textfield/textfield.jsp           |   2 +-
 .../apps/reference/components/forms/login.json     |   5 +
 .../reference/components/forms/login/edit.json     |  46 ++++++
 .../reference/components/forms/login/login.jsp     |  42 +++++
 .../forms/providers/requestparameters.json         |   6 +
 .../forms/providers/requestparameters/edit.json    |  21 +++
 .../requestparameters/requestparameters.jsp        |  26 +++
 .../components/forms/providers/suffixresource.json |   6 +
 .../forms/providers/suffixresource/edit.json       |  28 ++++
 .../providers/suffixresource/suffixresource.jsp    |  28 ++++
 .../cms/reference/forms/impl/FormHandlerTest.java  |  15 +-
 .../reference/forms/impl/FormRequestImplTest.java  |   3 +-
 .../actions/RequestPasswordResetActionTest.java    | 146 +++++++++++++++++
 .../impl/actions/ResetPasswordActionTest.java      | 175 +++++++++++++++++++++
 .../forms/impl/actions/SendEmailActionTest.java    |  18 ++-
 .../impl/actions/UpdateResourceActionTest.java     | 162 +++++++++++++++++++
 .../forms/impl/fields/HoneypotHandlerTest.java     |  99 ++++++++++++
 .../forms/impl/fields/TextfieldHandlerTest.java    |  47 +++++-
 .../RequestParametersValueProviderTest.java        |  76 +++++++++
 .../SuffixResourceFormValueProviderTest.java       | 156 ++++++++++++++++++
 reference/src/test/resources/form.json             |  42 ++++-
 47 files changed, 2183 insertions(+), 72 deletions(-)

diff --git a/reference/pom.xml b/reference/pom.xml
index 6c4b682..12e59ac 100644
--- a/reference/pom.xml
+++ b/reference/pom.xml
@@ -41,6 +41,26 @@
                 <groupId>biz.aQute.bnd</groupId>
                 <artifactId>bnd-maven-plugin</artifactId>
             </plugin>
+            <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <version>0.8.2</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>prepare-agent</goal>
+                        </goals>
+                    </execution>
+                    <!-- attached to Maven test phase -->
+                    <execution>
+                        <id>report</id>
+                        <phase>test</phase>
+                        <goals>
+                            <goal>report</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/FormConstants.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormConstants.java
new file mode 100644
index 0000000..182dcc9
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormConstants.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.reference.forms;
+
+public class FormConstants {
+
+    public static final String PATH_PROFILE = "profile";
+
+    public static final String PN_EMAIL = "email";
+
+    public static final String PN_SERVICE_USER = "serviceUser";
+
+    public static final String PN_ALLOWED_PROPERTIES = "allowedProperties";
+
+    public static final String PN_SUBPATH = "subpath";
+
+    public static final String SERVICE_USER = "slingcms-reference-usermanager";
+
+    public static final String PN_RESETTOKEN = "resettoken";
+    public static final String PN_RESETTIMEOUT = "resettimeout";
+
+    public static final String PN_PASSWORD = "passwordw";
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/FormUtils.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormUtils.java
new file mode 100644
index 0000000..3e77430
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormUtils.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.reference.forms;
+
+import java.util.stream.Stream;
+
+import org.apache.sling.api.resource.Resource;
+
+public class FormUtils {
+
+    public static final boolean handles(String[] supportedTypes, Resource resource) {
+        return Stream.of(supportedTypes).anyMatch(t -> t.equals(resource.getResourceType()));
+    }
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/FormHandler.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/FormHandler.java
index b75ad13..0b8de5b 100644
--- a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/FormHandler.java
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/FormHandler.java
@@ -36,6 +36,7 @@ import org.apache.sling.cms.Page;
 import org.apache.sling.cms.PageManager;
 import org.apache.sling.cms.ResourceTree;
 import org.apache.sling.cms.reference.forms.FormAction;
+import org.apache.sling.cms.reference.forms.FormActionResult;
 import org.apache.sling.cms.reference.forms.FormException;
 import org.apache.sling.cms.reference.forms.FormRequest;
 import org.osgi.service.component.annotations.Activate;
@@ -86,11 +87,15 @@ public class FormHandler extends SlingAllMethodsServlet {
             request.getSession().setAttribute(formRequest.getSessionId(), formRequest.getFormData());
             for (Resource actionResource : actionResources) {
                 log.debug("Finding action handler for: {}", actionResource);
-                for (FormAction action : formActions) {
-                    if (action.handles(actionResource)) {
-                        log.debug("Invoking handler: {}", action.getClass());
-                        action.handleForm(actionResource, formRequest);
-                        break;
+                FormAction action = formActions.stream().filter(fa -> fa.handles(actionResource)).findFirst()
+                        .orElse(null);
+                if (action != null) {
+                    FormActionResult result = action.handleForm(actionResource, formRequest);
+                    if (!result.isSucceeded()) {
+                        throw new FormException(
+                                "Failed to invoke action: " + action + " with message: " + result.getMessage());
+                    } else {
+                        log.debug("Successfully invoked action: {}", result.getMessage());
                     }
                 }
             }
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/CreateUserAction.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/CreateUserAction.java
new file mode 100644
index 0000000..83cfc4d
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/CreateUserAction.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.reference.forms.impl.actions;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.text.StringSubstitutor;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.cms.reference.forms.FormAction;
+import org.apache.sling.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormConstants;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.apache.sling.cms.reference.forms.FormUtils;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = { FormAction.class })
+@Designate(ocd = CreateUserAction.Config.class)
+public class CreateUserAction implements FormAction {
+
+    private static final Logger log = LoggerFactory.getLogger(CreateUserAction.class);
+    public static final String DEFAULT_RESOURCE_TYPE = "reference/components/forms/actions/createuser";
+    public static final String PROFILE_PROPERTIES = "profileProperties";
+    public static final String GROUPS = "groups";
+
+    private final ResourceResolverFactory factory;
+    private final Config config;
+
+    @Activate
+    public CreateUserAction(@Reference ResourceResolverFactory factory, Config config) {
+        this.factory = factory;
+        this.config = config;
+    }
+
+    @Override
+    public FormActionResult handleForm(final Resource actionResource, final FormRequest request) throws FormException {
+        final StringSubstitutor sub = new StringSubstitutor(request.getFormData());
+
+        final ValueMap properties = actionResource.getValueMap();
+
+        String username = request.getFormData().get("username", String.class);
+        String password = request.getFormData().get(FormConstants.PN_PASSWORD, String.class);
+
+        String intermediatePath = properties.get("intermediatePath", String.class);
+
+        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
+            return FormActionResult.failure("Empty username / password");
+        }
+
+        try {
+            try (ResourceResolver adminResolver = factory.getServiceResourceResolver(
+                    Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, FormConstants.SERVICE_USER))) {
+                JackrabbitSession session = (JackrabbitSession) adminResolver.adaptTo(Session.class);
+                final UserManager userManager = session.getUserManager();
+
+                if (userManager.getAuthorizable(new PrincipalImpl(username)) == null) {
+
+                    log.debug("Creating user {}", username);
+                    User user = userManager.createUser(username, password, new PrincipalImpl(username),
+                            intermediatePath);
+
+                    String[] groups = properties.get(GROUPS, new String[0]);
+                    for (String g : groups) {
+                        String groupName = sub.replace(g);
+                        Authorizable group = userManager.getAuthorizable(new PrincipalImpl(groupName));
+                        if (group == null || !group.isGroup()) {
+                            log.error("Could not find group {}", groupName);
+                            return FormActionResult.failure("Could not find group: " + groupName);
+                        } else {
+                            ((Group) group).addMember(user);
+                        }
+
+                    }
+                    log.debug("Updating profile for {}", username);
+                    updateProfile(adminResolver, user, properties.get(PROFILE_PROPERTIES, new String[0]),
+                            request.getFormData());
+
+                    log.debug("Saving changes!");
+                    adminResolver.commit();
+                    return FormActionResult.success("User " + username + " created successfully");
+                } else {
+                    log.error("Failed to create user, {} already exists", username);
+                    return FormActionResult.failure("User " + username + " already exists");
+                }
+            }
+        } catch (LoginException le) {
+            log.error("Failed to get user manager service user", le);
+            return FormActionResult.failure("Failed to get service user");
+        } catch (PersistenceException | RepositoryException e) {
+            log.error("Failed to create user " + username, e);
+            return FormActionResult.failure("Failed to create user " + username);
+        }
+    }
+
+    private void updateProfile(ResourceResolver adminResolver, User user, String @NotNull [] toset, ValueMap formData)
+            throws PersistenceException, RepositoryException {
+        if (toset.length > 0) {
+            Map<String, Object> properties = new HashMap<>();
+            Arrays.stream(toset).filter(k -> formData.keySet().contains(k))
+                    .forEach(k -> properties.put(k, formData.get(k)));
+            properties.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+            ResourceUtil.getOrCreateResource(adminResolver, user.getPath() + "/profile", properties,
+                    JcrConstants.NT_UNSTRUCTURED, false);
+        }
+
+    }
+
+    @Override
+    public boolean handles(Resource actionResource) {
+        return FormUtils.handles(config.supportedTypes(), actionResource);
+    }
+
+    @ObjectClassDefinition(name = "%cms.reference.createuser.name", description = "%cms.reference.createuser.description", localization = "OSGI-INF/l10n/bundle")
+    public @interface Config {
+
+        @AttributeDefinition(name = "%cms.reference.supportedTypes.name", description = "%cms.reference.supportedTypes.description", defaultValue = {
+                DEFAULT_RESOURCE_TYPE })
+        String[] supportedTypes() default { DEFAULT_RESOURCE_TYPE };
+    }
+
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/RequestPasswordResetAction.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/RequestPasswordResetAction.java
new file mode 100644
index 0000000..9ab6da6
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/RequestPasswordResetAction.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.reference.forms.impl.actions;
+
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+import javax.jcr.Session;
+import javax.jcr.ValueFactory;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.cms.reference.forms.FormAction;
+import org.apache.sling.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormConstants;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = { FormAction.class })
+@Designate(ocd = RequestPasswordResetAction.Config.class)
+public class RequestPasswordResetAction implements FormAction {
+
+    public static final String DEFAULT_RESOURCE_TYPE = "reference/components/forms/actions/requestpasswordreset";
+    public static final String PN_RESETTOKEN = "resettoken";
+    public static final String PN_RESETTIMEOUT = "resettimeout";
+    private static final Logger log = LoggerFactory.getLogger(RequestPasswordResetAction.class);
+    private ResourceResolverFactory factory;
+    private Config config;
+
+    @Activate
+    public RequestPasswordResetAction(@Reference ResourceResolverFactory factory, Config config) {
+        this.factory = factory;
+        this.config = config;
+    }
+
+    @Override
+    public FormActionResult handleForm(Resource actionResource, FormRequest request) throws FormException {
+        String email = request.getFormData().get(FormConstants.PN_EMAIL, String.class);
+        int resetTimeout = actionResource.getValueMap().get(PN_RESETTIMEOUT, Integer.class);
+
+        try (ResourceResolver adminResolver = factory.getServiceResourceResolver(
+                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, FormConstants.SERVICE_USER))) {
+
+            JackrabbitSession session = (JackrabbitSession) adminResolver.adaptTo(Session.class);
+            final UserManager userManager = session.getUserManager();
+
+            if (userManager.getAuthorizable(email) != null) {
+
+                User user = (User) userManager.getAuthorizable(email);
+
+                String resetToken = UUID.randomUUID().toString();
+                Calendar deadline = Calendar.getInstance();
+                deadline.add(Calendar.SECOND, resetTimeout);
+
+                ValueFactory vf = session.getValueFactory();
+
+                user.setProperty(PN_RESETTOKEN, vf.createValue(resetToken));
+                user.setProperty(PN_RESETTIMEOUT, vf.createValue(deadline));
+
+                request.getFormData().put(PN_RESETTOKEN, resetToken);
+
+                adminResolver.commit();
+
+            } else {
+                log.warn("Unable to find user {}", email);
+                return FormActionResult.failure("Unable to find user");
+            }
+        } catch (Exception e) {
+            throw new FormException("Failed to initiate password reset", e);
+        }
+        return FormActionResult.success("Reset token created");
+    }
+
+    @Override
+    public boolean handles(Resource actionResource) {
+        return Stream.of(config.supportedTypes()).anyMatch(t -> t.equals(actionResource.getResourceType()));
+    }
+
+    @ObjectClassDefinition(name = "%cms.reference.requestpasswordreset.name", description = "%cms.reference.requestpasswordreset.description", localization = "OSGI-INF/l10n/bundle")
+    public @interface Config {
+
+        @AttributeDefinition(name = "%cms.reference.supportedTypes.name", description = "%cms.reference.supportedTypes.description", defaultValue = {
+                DEFAULT_RESOURCE_TYPE })
+        String[] supportedTypes() default { DEFAULT_RESOURCE_TYPE };
+    }
+
+}
\ No newline at end of file
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/ResetPasswordAction.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/ResetPasswordAction.java
new file mode 100644
index 0000000..da70b34
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/ResetPasswordAction.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.reference.forms.impl.actions;
+
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.cms.reference.forms.FormAction;
+import org.apache.sling.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormConstants;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.apache.sling.cms.reference.forms.FormUtils;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = { FormAction.class })
+@Designate(ocd = ResetPasswordAction.Config.class)
+public class ResetPasswordAction implements FormAction {
+
+    public static final String DEFAULT_RESOURCE_TYPE = "reference/components/forms/actions/resetpassword";
+    private static final Logger log = LoggerFactory.getLogger(ResetPasswordAction.class);
+    private ResourceResolverFactory factory;
+    private Config config;
+
+    @Activate
+    public ResetPasswordAction(@Reference ResourceResolverFactory factory, Config config) {
+        this.factory = factory;
+        this.config = config;
+    }
+
+    @Override
+    public FormActionResult handleForm(Resource actionResource, FormRequest request) throws FormException {
+        String email = request.getFormData().get(FormConstants.PN_EMAIL, String.class);
+        String resetToken = request.getFormData().get(FormConstants.PN_RESETTOKEN, String.class);
+        String password = request.getFormData().get("password", String.class);
+
+        try (ResourceResolver adminResolver = factory.getServiceResourceResolver(
+                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, FormConstants.SERVICE_USER))) {
+
+            JackrabbitSession session = (JackrabbitSession) adminResolver.adaptTo(Session.class);
+            final UserManager userManager = session.getUserManager();
+
+            User user = (User) userManager.getAuthorizable(email);
+
+            if (user == null) {
+                return FormActionResult.failure("No user found for " + email);
+            }
+
+            String storedToken = getValue(user.getProperty(FormConstants.PN_RESETTOKEN), String.class);
+            Calendar resetTimeout = getValue(user.getProperty(FormConstants.PN_RESETTIMEOUT), Calendar.class);
+            if (storedToken == null || !storedToken.equals(resetToken)) {
+                return FormActionResult.failure("Failed to validate token");
+            }
+            if (Calendar.getInstance().after(resetTimeout)) {
+                return FormActionResult.failure("Timeout already passed");
+            }
+            user.changePassword(password);
+
+            log.debug("Saving changes!");
+            adminResolver.commit();
+
+            return FormActionResult.success("Password reset successfully!");
+        } catch (Exception e) {
+            throw new FormException("Failed to complete password reset", e);
+        }
+    }
+
+    private <E> E getValue(Value[] property, Class<E> clazz) throws IllegalStateException, RepositoryException {
+        if (property != null && property.length > 0) {
+            Value v = property[0];
+            if (clazz.isAssignableFrom(String.class)) {
+                return clazz.cast(v.getString());
+            }
+            if (clazz.isAssignableFrom(Calendar.class)) {
+                return clazz.cast(v.getDate());
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean handles(Resource actionResource) {
+        return FormUtils.handles(config.supportedTypes(), actionResource);
+    }
+
+    @ObjectClassDefinition(name = "%cms.reference.resetpassword.name", description = "%cms.reference.resetpassword.description", localization = "OSGI-INF/l10n/bundle")
+    public @interface Config {
+
+        @AttributeDefinition(name = "%cms.reference.supportedTypes.name", description = "%cms.reference.supportedTypes.description", defaultValue = {
+                DEFAULT_RESOURCE_TYPE })
+        String[] supportedTypes() default { DEFAULT_RESOURCE_TYPE };
+    }
+
+}
\ No newline at end of file
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailAction.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailAction.java
index 630e129..cc20283 100644
--- a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailAction.java
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailAction.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sling.cms.reference.forms.impl.actions;
 
+import java.util.stream.Stream;
+
 import javax.mail.MessagingException;
 import javax.mail.internet.MimeMessage;
 
@@ -28,14 +30,20 @@ import org.apache.sling.cms.reference.forms.FormException;
 import org.apache.sling.cms.reference.forms.FormRequest;
 import org.apache.sling.commons.messaging.mail.MailService;
 import org.jetbrains.annotations.NotNull;
+import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @Component(service = { FormAction.class })
+@Designate(ocd = SendEmailAction.Config.class)
 public class SendEmailAction implements FormAction {
 
+    public static final String DEFAULT_RESOURCE_TYPE = "reference/components/forms/actions/sendemail";
     public static final String FROM = "from";
     private static final Logger log = LoggerFactory.getLogger(SendEmailAction.class);
 
@@ -43,7 +51,14 @@ public class SendEmailAction implements FormAction {
     public static final String SUBJECT = "subject";
     public static final String TO = "to";
 
-    private MailService mailService;
+    private final MailService mailService;
+    private Config config;
+
+    @Activate
+    public SendEmailAction(@Reference MailService mailService, Config config) {
+        this.mailService = mailService;
+        this.config = config;
+    }
 
     @Override
     public FormActionResult handleForm(final Resource actionResource, final FormRequest request) throws FormException {
@@ -71,13 +86,15 @@ public class SendEmailAction implements FormAction {
     }
 
     @Override
-    public boolean handles(final Resource actionResource) {
-        return "reference/components/forms/actions/sendemail".equals(actionResource.getResourceType());
+    public boolean handles(Resource actionResource) {
+        return Stream.of(config.supportedTypes()).anyMatch(t -> t.equals(actionResource.getResourceType()));
     }
 
-    @Reference
-    public void setMailService(MailService mailService) {
-        this.mailService = mailService;
-    }
+    @ObjectClassDefinition(name = "%cms.reference.sendemail.name", description = "%cms.reference.sendemail.description", localization = "OSGI-INF/l10n/bundle")
+    public @interface Config {
 
+        @AttributeDefinition(name = "%cms.reference.supportedTypes.name", description = "%cms.reference.supportedTypes.description", defaultValue = {
+                DEFAULT_RESOURCE_TYPE })
+        String[] supportedTypes() default { DEFAULT_RESOURCE_TYPE };
+    }
 }
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateProfileAction.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateProfileAction.java
index 53d24d9..4aba5cc 100644
--- a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateProfileAction.java
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateProfileAction.java
@@ -34,6 +34,7 @@ import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.cms.reference.forms.FormAction;
 import org.apache.sling.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormConstants;
 import org.apache.sling.cms.reference.forms.FormException;
 import org.apache.sling.cms.reference.forms.FormRequest;
 import org.osgi.service.component.annotations.Component;
@@ -51,51 +52,53 @@ public class UpdateProfileAction implements FormAction {
         String userId = resolver.getUserID();
         JackrabbitSession session = (JackrabbitSession) resolver.adaptTo(Session.class);
 
-        if (session != null) {
-            try {
-                final UserManager userManager = session.getUserManager();
-                if (userManager.getAuthorizable(userId) != null) {
-
-                    User user = (User) userManager.getAuthorizable(userId);
-                    log.debug("Updating profile for {}", userId);
-
-                    String subpath = actionResource.getValueMap().get("subpath", "profile");
-                    ValueFactory valueFactory = session.getValueFactory();
-
-                    for (Entry<String, Object> e : request.getFormData().entrySet()) {
-                        Value value = null;
-                        if (e.getValue() instanceof String[]) {
-                            user.setProperty(subpath + "/" + e.getKey(), Arrays.stream((String[]) e.getValue())
-                                    .map(valueFactory::createValue).collect(Collectors.toList()).toArray(new Value[0]));
-                        } else {
-                            if (e.getValue() instanceof Calendar) {
-                                value = valueFactory.createValue((Calendar) e.getValue());
-                            } else if (e.getValue() instanceof Double) {
-                                value = valueFactory.createValue((Double) e.getValue());
-                            } else if (e.getValue() instanceof Integer) {
-                                value = valueFactory.createValue((Double) e.getValue());
-                            } else {
-                                value = valueFactory.createValue((String) e.getValue());
-                            }
-                            user.setProperty(subpath + "/" + e.getKey(), value);
-                        }
-                    }
-                    log.debug("Saving changes!");
-                    resolver.commit();
+        if (session == null) {
+            log.warn("Failed to get session for {}", userId);
+            return FormActionResult.failure("Failed to get session for " + userId);
+        }
+        try {
+            final UserManager userManager = session.getUserManager();
+            if (userManager.getAuthorizable(userId) == null) {
+
+                log.warn("No profile found for {}", userId);
+                return FormActionResult.failure("No profile found for " + userId);
+            }
+
+            User user = (User) userManager.getAuthorizable(userId);
+            log.debug("Updating profile for {}", userId);
 
-                    return FormActionResult.success("Profile Updated");
+            String subpath = actionResource.getValueMap().get(FormConstants.PN_SUBPATH, FormConstants.PATH_PROFILE);
+            ValueFactory valueFactory = session.getValueFactory();
+
+            for (Entry<String, Object> e : request.getFormData().entrySet()) {
+                Value value = null;
+                if (e.getValue() instanceof String[]) {
+                    Value[] values = Arrays.stream(((String[]) e.getValue())).map(valueFactory::createValue)
+                            .collect(Collectors.toList()).toArray(new Value[0]);
+                    user.setProperty(subpath + "/" + e.getKey(), values);
                 } else {
-                    log.warn("No profile found for {}", userId);
-                    return FormActionResult.failure("No profile found for " + userId);
+                    if (e.getValue() instanceof Calendar) {
+                        value = valueFactory.createValue((Calendar) e.getValue());
+                    } else if (e.getValue() instanceof Double) {
+                        value = valueFactory.createValue((Double) e.getValue());
+                    } else if (e.getValue() instanceof Integer) {
+                        value = valueFactory.createValue((Double) e.getValue());
+                    } else {
+                        value = valueFactory.createValue((String) e.getValue());
+                    }
+                    user.setProperty(subpath + "/" + e.getKey(), value);
                 }
-            } catch (RepositoryException | PersistenceException e) {
-                log.warn("Failed to update profile for {}", userId, e);
-                return FormActionResult.failure("Failed to update profile for " + userId);
             }
-        } else {
-            log.warn("Failed to get session for {}", userId);
-            return FormActionResult.failure("Failed to get session for " + userId);
+            log.debug("Saving changes!");
+            resolver.commit();
+
+            return FormActionResult.success("Profile Updated");
+
+        } catch (RepositoryException | PersistenceException e) {
+            log.warn("Failed to update profile for {}", userId, e);
+            return FormActionResult.failure("Failed to update profile for " + userId);
         }
+
     }
 
     @Override
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateResourceAction.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateResourceAction.java
new file mode 100644
index 0000000..b6b4203
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateResourceAction.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.reference.forms.impl.actions;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.text.StringSubstitutor;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.cms.reference.forms.FormAction;
+import org.apache.sling.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormConstants;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = FormAction.class, immediate = true)
+public class UpdateResourceAction implements FormAction {
+
+    private static final Logger log = LoggerFactory.getLogger(UpdateResourceAction.class);
+
+    private ResourceResolverFactory factory;
+
+    @Activate
+    public UpdateResourceAction(@Reference ResourceResolverFactory factory) {
+        this.factory = factory;
+    }
+
+    @Override
+    public FormActionResult handleForm(Resource actionResource, FormRequest request) throws FormException {
+        log.trace("handleForm");
+
+        ValueMap properties = actionResource.getValueMap();
+
+        String[] allowedProperties = properties.get(FormConstants.PN_ALLOWED_PROPERTIES, String[].class);
+        ResourceResolver resolver = null;
+        try {
+            resolver = getResourceResolver(actionResource);
+            StringSubstitutor sub = new StringSubstitutor(request.getFormData());
+
+            String path = sub.replace(properties.get("path", String.class));
+            log.debug("Upserting resource at path: {}", path);
+            Resource resource = ResourceUtil.getOrCreateResource(resolver, path,
+                    Collections.singletonMap(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED),
+                    JcrConstants.NT_UNSTRUCTURED, false);
+
+            ModifiableValueMap mvm = resource.adaptTo(ModifiableValueMap.class);
+            Map<String, Object> formData = request.getFormData();
+            if (allowedProperties != null && allowedProperties.length > 0) {
+                Arrays.stream(allowedProperties).filter(formData::containsKey).forEach(p -> {
+                    log.debug("Setting property {}", p);
+                    mvm.put(p, formData.get(p));
+                });
+            } else {
+                mvm.putAll(formData);
+            }
+            resolver.commit();
+            log.debug("Successfully persisted resource");
+            return FormActionResult.success("Updated resource");
+        } catch (LoginException | PersistenceException e) {
+            throw new FormException("Failed to update resource", e);
+        } finally {
+            if (StringUtils.isNotBlank(properties.get(FormConstants.PN_SERVICE_USER, String.class)) && resolver != null) {
+                resolver.close();
+            }
+        }
+    }
+
+    private ResourceResolver getResourceResolver(Resource actionResource) throws LoginException {
+        String serviceUser = actionResource.getValueMap().get(FormConstants.PN_SERVICE_USER, String.class);
+        if (StringUtils.isNotBlank(serviceUser)) {
+            return factory.getServiceResourceResolver(
+                    Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, serviceUser));
+        } else {
+            return actionResource.getResourceResolver();
+        }
+
+    }
+
+    @Override
+    public boolean handles(Resource actionResource) {
+        return "reference/components/forms/actions/updateresource".equals(actionResource.getResourceType());
+    }
+
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/RequestParametersValueProvider.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/RequestParametersValueProvider.java
new file mode 100644
index 0000000..ca08dee
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/RequestParametersValueProvider.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.reference.forms.impl.providers;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.cms.reference.forms.FormValueProvider;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = FormValueProvider.class)
+public class RequestParametersValueProvider implements FormValueProvider {
+
+    private static final Logger log = LoggerFactory.getLogger(RequestParametersValueProvider.class);
+
+    @Override
+    public void loadValues(SlingHttpServletRequest request, Resource providerResource, Map<String, Object> formData) {
+        log.trace("loadFormData");
+        String[] parameters = providerResource.getValueMap().get("allowedParameters", String[].class);
+        if (parameters != null) {
+            Arrays.stream(parameters).forEach(p -> {
+                if (request.getParameter(p) != null) {
+                    if (request.getParameterValues(p).length > 1) {
+                        formData.put(p, request.getParameterValues(p));
+                    } else {
+                        formData.put(p, request.getParameter(p));
+                    }
+                }
+            });
+        }
+
+    }
+
+    @Override
+    public boolean handles(Resource valueProviderResource) {
+        return "reference/components/forms/providers/requestparameters".equals(valueProviderResource.getResourceType());
+    }
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/SuffixResourceFormValueProvider.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/SuffixResourceFormValueProvider.java
new file mode 100644
index 0000000..15e57c6
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/SuffixResourceFormValueProvider.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.reference.forms.impl.providers;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import org.apache.commons.text.StringSubstitutor;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.cms.reference.forms.FormConstants;
+import org.apache.sling.cms.reference.forms.FormValueProvider;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = FormValueProvider.class)
+public class SuffixResourceFormValueProvider implements FormValueProvider {
+
+    private static final Logger log = LoggerFactory.getLogger(SuffixResourceFormValueProvider.class);
+
+    @Override
+    public void loadValues(SlingHttpServletRequest request, Resource providerResource, Map<String, Object> formData) {
+        log.trace("loadValues");
+        Resource suffixResource = request.getRequestPathInfo().getSuffixResource();
+
+        ValueMap providerProperties = providerResource.getValueMap();
+        StringSubstitutor sub = new StringSubstitutor(formData);
+        String basePath = sub.replace(providerProperties.get("basePath", String.class));
+        String[] allowedProperties = providerProperties.get(FormConstants.PN_ALLOWED_PROPERTIES, String[].class);
+        if (suffixResource != null && suffixResource.getPath().startsWith(basePath)) {
+            ValueMap suffixProperties = suffixResource.getValueMap();
+            if (allowedProperties != null && allowedProperties.length > 0) {
+                Arrays.stream(allowedProperties).filter(suffixProperties::containsKey)
+                        .forEach(p -> formData.put(p, suffixProperties.get(p)));
+            } else {
+                formData.putAll(suffixProperties);
+            }
+            formData.put("suffixResource", suffixResource.getPath());
+        }
+
+    }
+
+    @Override
+    public boolean handles(Resource valueProviderResource) {
+        return "reference/components/forms/providers/suffixresource".equals(valueProviderResource.getResourceType());
+    }
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/UserProfileFormValueProvider.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/UserProfileFormValueProvider.java
index 4219be9..323b51a 100644
--- a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/UserProfileFormValueProvider.java
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/UserProfileFormValueProvider.java
@@ -32,6 +32,7 @@ import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.reference.forms.FormConstants;
 import org.apache.sling.cms.reference.forms.FormValueProvider;
 import org.osgi.service.component.annotations.Component;
 import org.slf4j.Logger;
@@ -53,7 +54,7 @@ public class UserProfileFormValueProvider implements FormValueProvider {
                 UserManager userManager = session.getUserManager();
                 User user = (User) userManager.getAuthorizable(userId);
 
-                String subpath = providerResource.getValueMap().get("subpath", "profile");
+                String subpath = providerResource.getValueMap().get(FormConstants.PN_SUBPATH, FormConstants.PATH_PROFILE);
                 log.debug("Loading profile data from: {}/{}", user.getPath(), subpath);
 
                 Iterator<String> keys = user.getPropertyNames(subpath);
diff --git a/reference/src/main/resources/OSGI-INF/l10n/bundle.properties b/reference/src/main/resources/OSGI-INF/l10n/bundle.properties
index 0911dc9..5b263ae 100644
--- a/reference/src/main/resources/OSGI-INF/l10n/bundle.properties
+++ b/reference/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -23,7 +23,7 @@
 # the Sling SCR plugin
 
 ## Search Service Entries
-cms.reference.search.name=Apache Sling Reference Search Configuration
+cms.reference.search.name=Apache Sling CMS - Reference Search Configuration
 cms.reference.search.description=Configuration for the Reference \
 Search component
 
@@ -32,7 +32,7 @@ searchServiceUsername.description=The name of a service user to use \
 to use for searching, if not specified, the current user will be used
 
 # Send Email
-cms.reference.sendemail.name=Apache Sling Reference Send Email Form Action
+cms.reference.sendemail.name=Apache Sling CMS - Reference Send Email Form Action
 cms.reference.sendemail.description=A reference form action for sending a \
 simple text email
 
@@ -44,7 +44,6 @@ cms.reference.sendemail.smtpPort.name=SMTP Port
 cms.reference.sendemail.smtpPort.description=The port upon which to connect \
 to the SMTP server
 
-
 cms.reference.sendemail.tlsEnabled.name=TLS Enabled
 cms.reference.sendemail.tlsEnabled.description=Whether or not TLS security is \
 enabled
@@ -55,4 +54,21 @@ to the SMTP server
 
 cms.reference.sendemail.password.name=Password
 cms.reference.sendemail.password.description=The password to use when connecting \
-to the SMTP server
\ No newline at end of file
+to the SMTP server
+
+# Form configuration titles
+cms.reference.supportedTypes.name=Supported Types
+cms.reference.supportedTypes.description=The resource types supported by this action
+
+cms.reference.createuser.name=Apache Sling CMS - Reference Create User
+cms.reference.createuser.description=Form action for creating a user
+
+cms.reference.requestpasswordreset.name=Apache Sling CMS - Reference Password Reset Request
+cms.reference.requestpasswordreset.description=Form action for initiating a password \
+reset request
+
+cms.reference.resetpassword.name=Apache Sling CMS - Reference Password Reset
+cms.reference.requestpasswordreset.description=Form action for resetting a user password
+
+cms.reference.sendemail.name=Apache Sling CMS - Reference Send Email
+cms.reference.requestpasswordreset.description=Form action for sending an email
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser.json
new file mode 100644
index 0000000..7e59253
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Create User",
+    "componentType": "Form Action"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser/createuser.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser/createuser.jsp
new file mode 100644
index 0000000..b6c450f
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser/createuser.jsp
@@ -0,0 +1,32 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<c:if test="${cmsEditEnabled == 'true'}">
+    <h3>Create User</h3>
+    <dl>
+        <dt>Group Membership</dt>
+        <dd>${sling:encode(fn:join(properties.groups,','),'HTML')}</dd>
+        <dt>Intermediate Path</dt>
+        <dd>${sling:encode(properties.intermediatePath,'HTML')}</dd>
+        <dt>Service User</dt>
+        <dd>${sling:encode(properties.serviceUser,'HTML')}</dd>
+        <dt>Set Profile Properties</dt>
+        <dd>${sling:encode(fn:join(properties.profileProperties,','),'HTML')}</dd>
+    </dl>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser/edit.json
new file mode 100644
index 0000000..3af280d
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser/edit.json
@@ -0,0 +1,47 @@
+{
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "groups": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/repeating",
+            "label": "Group Membership",
+            "name": "groups"
+        },
+        "groupsTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "groups@TypeHint",
+            "value": "String[]"
+        },
+        "profileProperties": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/repeating",
+            "label": "Profile Properties",
+            "name": "profileProperties"
+        },
+        "profilePropertiesTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "profileProperties@TypeHint",
+            "value": "String[]"
+        },
+        "intermediatePath": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Intermediate Path",
+            "name": "intermediatePath",
+            "required": true
+        },
+        "serviceUser": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Service User",
+            "name": "serviceUser",
+            "required": true
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/requestpasswordreset/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/requestpasswordreset/edit.json
new file mode 100644
index 0000000..760ab09
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/requestpasswordreset/edit.json
@@ -0,0 +1,16 @@
+{
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "resettimeout": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Reset Timeout",
+            "name": "resettimeout",
+            "type": "number"
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/requestpasswordreset/requestpasswordreset.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/requestpasswordreset/requestpasswordreset.jsp
new file mode 100644
index 0000000..becd97e
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/requestpasswordreset/requestpasswordreset.jsp
@@ -0,0 +1,26 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<c:if test="${cmsEditEnabled == 'true'}">
+    <h3>Request Password Reset</h3>
+    <dl>
+        <dt>Reset Timeout</dt>
+        <dd>${sling:encode(properties.resettimeout,'HTML')}</dd>
+    </dl>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword.json
new file mode 100644
index 0000000..0635a1b
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Reset Password",
+    "componentType": "Form Action"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword/edit.json
new file mode 100644
index 0000000..4f8a34b
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword/edit.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "No need to edit"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword/resetpassword.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword/resetpassword.jsp
new file mode 100644
index 0000000..7c9b003
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword/resetpassword.jsp
@@ -0,0 +1,22 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<c:if test="${cmsEditEnabled == 'true'}">
+    <h3>Reset Password</h3>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateresource.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateresource.json
new file mode 100644
index 0000000..2ec03ee
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateresource.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Update Resource",
+    "componentType": "Form Action"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateresource/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateresource/edit.json
new file mode 100644
index 0000000..a67511b
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateresource/edit.json
@@ -0,0 +1,32 @@
+{
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "allowedProperties": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/repeating",
+            "label": "Allowed Properties",
+            "name": "allowedProperties"
+        },
+        "allowedPropertiesTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "allowedProperties@TypeHint",
+            "value": "String[]"
+        },
+        "path": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/path",
+            "label": "Path",
+            "name": "path",
+            "required": true
+        },
+        "serviceUser": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Service User",
+            "name": "serviceUser"
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateresource/updateresource.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateresource/updateresource.jsp
new file mode 100644
index 0000000..3389be7
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateresource/updateresource.jsp
@@ -0,0 +1,30 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<c:if test="${cmsEditEnabled == 'true'}">
+    <h3>Update Resource</h3>
+    <dl>
+        <dt>Allowed Properties</dt>
+        <dd>${sling:encode(fn:join(properties.allowedProperties,','),'HTML')}</dd>
+        <dt>Path</dt>
+        <dd>${sling:encode(properties.path,'HTML')}</dd>
+        <dt>Service User</dt>
+        <dd>${sling:encode(properties.serviceUser,'HTML')}</dd>
+    </dl>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textarea/textarea.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textarea/textarea.jsp
index e046a87..e2ca9cf 100644
--- a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textarea/textarea.jsp
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textarea/textarea.jsp
@@ -39,9 +39,9 @@
             <c:set var="fieldValue" value="${properties.value}" />
         </c:when>
     </c:choose>
-    <textarea class="${formConfig.fieldClass}" id="${properties.name}" name="${properties.name}" ${properties.required ? 'required="required"' : ''}
+    <textarea class="${sling:encode(formConfig.fieldClass,'HTML')}" id="${sling:encode(properties.name,'HTML')}" name="${sling:encode(properties.name,'HTML')}" ${properties.required ? 'required="required"' : ''}
         <c:forEach var="attr" items="${properties.additionalAttributes}">
             ${fn:split(attr,'\\=')[0]}="${fn:split(attr,'\\=')[1]}"
         </c:forEach>
-        >${fieldValue}</textarea>
+        >${sling:encode(fieldValue,'HTML')}</textarea>
 </div>
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textfield/textfield.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textfield/textfield.jsp
index 09d5cfa..4adc29c 100644
--- a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textfield/textfield.jsp
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textfield/textfield.jsp
@@ -41,7 +41,7 @@
             <c:set var="fieldValue" value="${properties.value}" />
         </c:when>
     </c:choose>
-    <input type="${properties.type}" class="${formConfig.fieldClass}" id="${properties.name}" name="${properties.name}" value="${fieldValue}" ${not empty properties.pattern ? patternStr : ''} ${not empty properties.placeholder ? placeholderStr : ''} ${properties.required ? 'required="required"' : ''}
+    <input type="${sling:encode(properties.type,'HTML_ATTR')}" class="${sling:encode(formConfig.fieldClass,'HTML_ATTR')}" id="${sling:encode(properties.name,'HTML_ATTR')}" name="${sling:encode(properties.name,'HTML_ATTR')}" value="${sling:encode(fieldValue,'HTML_ATTR')}" ${not empty properties.pattern ? patternStr : ''} ${not empty properties.placeholder ? placeholderStr : ''} ${properties.required ? 'required="required"' : ''}
         <c:forEach var="attr" items="${properties.additionalAttributes}">
             ${fn:split(attr,'\\=')[0]}="${fn:split(attr,'\\=')[1]}"
         </c:forEach> 
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/login.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/login.json
new file mode 100644
index 0000000..39fbbdc
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/login.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Login",
+    "componentType": "General"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/login/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/login/edit.json
new file mode 100644
index 0000000..1f19887
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/login/edit.json
@@ -0,0 +1,46 @@
+{
+	"jcr:primaryType": "nt:unstructured",
+	"sling:resourceType": "sling-cms/components/editor/slingform",
+	"button": "Save",
+	"fields": {
+		"jcr:primaryType": "nt:unstructured",
+		"sling:resourceType": "sling-cms/components/general/container",
+		"errorMessage": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "sling-cms/components/editor/fields/richtext",
+			"label": "Error Message",
+			"name": "errorMessage",
+			"required": true
+		},
+		"usernameLabel": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "sling-cms/components/editor/fields/text",
+			"defaultValue": "Submit",
+			"label": "Username Label",
+			"name": "usernameLabel",
+			"required": true
+		},
+		"passwordLabel": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "sling-cms/components/editor/fields/text",
+			"defaultValue": "Submit",
+			"label": "Password Label",
+			"name": "passwordLabel",
+			"required": true
+		},
+		"submitLabel": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "sling-cms/components/editor/fields/text",
+			"defaultValue": "Submit",
+			"label": "Submit Label",
+			"name": "submitLabel",
+			"required": true
+		},
+		"successPage": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "sling-cms/components/editor/fields/path",
+			"label": "Success Page",
+			"name": "successPage"
+		}
+	}
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/login/login.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/login/login.jsp
new file mode 100644
index 0000000..15d0957
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/login/login.jsp
@@ -0,0 +1,42 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */ --%>
+<%@include file="/libs/sling-cms/global.jsp"%>
+<sling:adaptTo adaptable="${resource}" adaptTo="org.apache.sling.cms.PageManager" var="pageManager" />
+<sling:adaptTo adaptable="${resource}" adaptTo="org.apache.sling.cms.ComponentPolicyManager" var="componentPolicyMgr" />
+<c:set var="formConfig" value="${componentPolicyMgr.componentPolicy.componentConfigs['reference/components/forms/form'].valueMap}" />
+<form class="${formConfig.formClass}" action="${pageManager.page.path}.allowpost.html/j_security_check" method="post" data-analytics-id="Login Form">  
+    <c:if test="${not empty param.j_reason}">
+        <div class="${formConfig.alertClass}">
+            ${sling:encode(properties.errorMessage,'HTML')}
+        </div>
+    </c:if>
+    <div class="${formConfig.fieldGroupClass}">
+        <label for="j_username" class="label">${sling:encode(properties.usernameLabel,'HTML')} <span class="${formConfig.fieldRequiredClass}">*</span></label>
+        <input type="text" class="${formConfig.fieldClass}" required="required" name="j_username" />
+    </div>
+    <div class="${formConfig.fieldGroupClass}">
+        <label for="j_password" class="label">${sling:encode(properties.passwordLabel,'HTML')} <span class="${formConfig.fieldRequiredClass}">*</span></label>
+        <input type="password" class="${formConfig.fieldClass}" required="required" name="j_password" />
+    </div>
+    <input type="hidden" name="resource" value="${sling:encode(properties.successPage,'HTML_ATTR')}.html" />
+    <input type="hidden" name="j_validate" value="true" />
+    <div class="${formConfig.fieldGroupClass}">
+        <button class="${formConfig.submitClass}">${sling:encode(properties.submitLabel,'HTML')}</button>
+    </div>
+</form>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters.json
new file mode 100644
index 0000000..802f8db
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters.json
@@ -0,0 +1,6 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Request Parameters",
+    "componentType": "Form Value Provider",
+    "reloadPage": true
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters/edit.json
new file mode 100644
index 0000000..1fccbaf
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters/edit.json
@@ -0,0 +1,21 @@
+{
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "subpath": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/repeating",
+            "label": "Allowed Parameters",
+            "name": "allowedParameters"
+        },
+        "subpathTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "allowedParameters@TypeHint",
+            "value": "String[]"
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters/requestparameters.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters/requestparameters.jsp
new file mode 100644
index 0000000..aad2f64
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters/requestparameters.jsp
@@ -0,0 +1,26 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<c:if test="${cmsEditEnabled == 'true'}">
+    <h3>Request Parameters</h3>
+    <dl>
+        <dt>Allowed Parameters</dt>
+        <dd>${sling:encode(fn:join(properties.allowedParameters,','),'HTML')}</dd>
+    </dl>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource.json
new file mode 100644
index 0000000..e497a9c
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource.json
@@ -0,0 +1,6 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Suffix Resource",
+    "componentType": "Form Value Provider",
+    "reloadPage": true
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource/edit.json
new file mode 100644
index 0000000..1e67bbb
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource/edit.json
@@ -0,0 +1,28 @@
+{
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "allowedProperties": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/repeating",
+            "label": "Allowed Properties",
+            "name": "allowedProperties"
+        },
+        "allowedPropertiesTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "allowedProperties@TypeHint",
+            "value": "String[]"
+        },
+        "basePath": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/path",
+            "label": "Base Path",
+            "name": "basePath",
+            "required": true
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource/suffixresource.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource/suffixresource.jsp
new file mode 100644
index 0000000..c702d5f
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource/suffixresource.jsp
@@ -0,0 +1,28 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<c:if test="${cmsEditEnabled == 'true'}">
+    <h3>Suffix Resource</h3>
+    <dl>
+        <dt>Allowed Properties</dt>
+        <dd>${sling:encode(fn:join(properties.allowedProperties,','),'HTML')}</dd>
+        <dt>Base Path</dt>
+        <dd>${sling:encode(properties.basePath,'HTML')}</dd>
+    </dl>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormHandlerTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormHandlerTest.java
index 048d08d..f8b80ce 100644
--- a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormHandlerTest.java
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormHandlerTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.never;
 
 import java.io.IOException;
+import java.lang.annotation.Annotation;
 import java.util.Arrays;
 
 import javax.servlet.ServletException;
@@ -64,11 +65,21 @@ public class FormHandlerTest {
         formRequest = new FormRequestImpl(context.request(), null,
                 Arrays.asList(new SelectionHandler(), new TextareaHandler(), new TextfieldHandler()));
 
-        final SendEmailAction sendEmailAction = new SendEmailAction();
         mailService = Mockito.mock(MailService.class);
         Mockito.when(mailService.getMessageBuilder()).thenReturn(new MockMessageBuilder());
-        sendEmailAction.setMailService(mailService);
+        final SendEmailAction sendEmailAction = new SendEmailAction(mailService, new SendEmailAction.Config() {
 
+            @Override
+            public Class<? extends Annotation> annotationType() {
+                return null;
+            }
+
+            @Override
+            public String[] supportedTypes() {
+                return new String[] { SendEmailAction.DEFAULT_RESOURCE_TYPE };
+            }
+
+        });
         formHandler = new FormHandler(Arrays.asList(sendEmailAction)) {
             private static final long serialVersionUID = 1L;
 
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormRequestImplTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormRequestImplTest.java
index 824056d..e90933f 100644
--- a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormRequestImplTest.java
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormRequestImplTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import java.io.ByteArrayInputStream;
 import java.util.Arrays;
 
 import com.google.common.collect.ImmutableMap;
@@ -49,7 +50,7 @@ public class FormRequestImplTest {
                 .setParameterMap(ImmutableMap.<String, Object>builder().put("requiredtextarea", "Hello World!")
                         .put("singleselect", "Hello World!").put("anotherkey", "Hello World!").put("money", "123")
                         .put("patternfield", "123").put("double", "2.7").put("integer", "2")
-                        .put("datefield", "2019-02-02").build());
+                        .put("file", new ByteArrayInputStream(new byte[0])).put("datefield", "2019-02-02").build());
 
         formRequest = new FormRequestImpl(context.request(), null,
                 Arrays.asList(new SelectionHandler(), new TextareaHandler(), new TextfieldHandler()));
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/RequestPasswordResetActionTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/RequestPasswordResetActionTest.java
new file mode 100644
index 0000000..78ee756
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/RequestPasswordResetActionTest.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.reference.forms.impl.actions;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.ValueFactory;
+import javax.jcr.ValueFormatException;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.value.AbstractValueFactory;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.apache.sling.cms.reference.forms.impl.FormRequestImpl;
+import org.apache.sling.cms.reference.forms.impl.actions.RequestPasswordResetAction.Config;
+import org.apache.sling.servlethelpers.MockSlingHttpServletRequest;
+import org.apache.sling.testing.resourceresolver.MockResource;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class RequestPasswordResetActionTest {
+
+    private ResourceResolverFactory factory;
+    private ResourceResolver resolver;
+
+    @Before
+    public void init() throws FormException, LoginException, AccessDeniedException,
+            UnsupportedRepositoryOperationException, RepositoryException {
+
+        factory = Mockito.mock(ResourceResolverFactory.class);
+
+        resolver = Mockito.mock(ResourceResolver.class);
+        Mockito.when(factory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resolver);
+
+        JackrabbitSession session = Mockito.mock(JackrabbitSession.class);
+        Mockito.when(resolver.adaptTo(Mockito.any())).thenReturn(session);
+
+        ValueFactory vf = new AbstractValueFactory() {
+
+            @Override
+            protected void checkPathFormat(String pathValue) throws ValueFormatException {
+                // TODO Auto-generated method stub
+
+            }
+
+            @Override
+            protected void checkNameFormat(String nameValue) throws ValueFormatException {
+                // TODO Auto-generated method stub
+
+            }
+
+        };
+        Mockito.when(session.getValueFactory()).thenReturn(vf);
+
+        UserManager userManager = Mockito.mock(UserManager.class);
+        Mockito.when(session.getUserManager()).thenReturn(userManager);
+
+        Mockito.when(userManager.getAuthorizable(Mockito.eq("test@email.com"))).thenReturn(Mockito.mock(User.class));
+
+    }
+
+    @Test
+    public void testHandleForm() throws FormException {
+
+        RequestPasswordResetAction action = new RequestPasswordResetAction(factory, null);
+
+        FormRequest request = new FormRequestImpl(new MockSlingHttpServletRequest(resolver), null, null);
+        request.getFormData().put("email", "test@email.com");
+
+        Resource actionResource = new MockResource("/content",
+                Collections.singletonMap(RequestPasswordResetAction.PN_RESETTIMEOUT, 2), null);
+
+        FormActionResult result = action.handleForm(actionResource, request);
+        assertTrue(result.isSucceeded());
+
+    }
+
+    @Test
+    public void testNoUser() throws FormException {
+
+        RequestPasswordResetAction action = new RequestPasswordResetAction(factory, null);
+
+        FormRequest request = new FormRequestImpl(new MockSlingHttpServletRequest(resolver), null, null);
+        request.getFormData().put("email", "test1@email.com");
+
+        Resource actionResource = new MockResource("/content",
+                Collections.singletonMap(RequestPasswordResetAction.PN_RESETTIMEOUT, 2), null);
+
+        FormActionResult result = action.handleForm(actionResource, request);
+        assertFalse(result.isSucceeded());
+
+    }
+
+    @Test
+    public void testHandles() throws FormException {
+        Config config = new Config() {
+            @Override
+            public Class<? extends Annotation> annotationType() {
+                return null;
+            }
+
+            @Override
+            public String[] supportedTypes() {
+                return new String[] { RequestPasswordResetAction.DEFAULT_RESOURCE_TYPE };
+            }
+        };
+        RequestPasswordResetAction action = new RequestPasswordResetAction(null, config);
+        Resource validResource = Mockito.mock(Resource.class);
+        Mockito.when(validResource.getResourceType()).thenReturn(RequestPasswordResetAction.DEFAULT_RESOURCE_TYPE);
+        assertTrue(action.handles(validResource));
+
+        Resource inValidResource = Mockito.mock(Resource.class);
+        Mockito.when(inValidResource.getResourceType()).thenReturn("something/else");
+        assertFalse(action.handles(inValidResource));
+    }
+
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/ResetPasswordActionTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/ResetPasswordActionTest.java
new file mode 100644
index 0000000..3f86f86
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/ResetPasswordActionTest.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.reference.forms.impl.actions;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.annotation.Annotation;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.ValueFormatException;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.value.AbstractValueFactory;
+import org.apache.jackrabbit.value.DateValue;
+import org.apache.jackrabbit.value.StringValue;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormConstants;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.apache.sling.cms.reference.forms.impl.FormRequestImpl;
+import org.apache.sling.cms.reference.forms.impl.actions.RequestPasswordResetAction.Config;
+import org.apache.sling.servlethelpers.MockSlingHttpServletRequest;
+import org.apache.sling.testing.resourceresolver.MockResource;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class ResetPasswordActionTest {
+
+    private ResourceResolverFactory factory;
+    private ResourceResolver resolver;
+
+    @Before
+    public void init() throws FormException, LoginException, AccessDeniedException,
+            UnsupportedRepositoryOperationException, RepositoryException {
+
+        factory = Mockito.mock(ResourceResolverFactory.class);
+
+        resolver = Mockito.mock(ResourceResolver.class);
+        Mockito.when(factory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resolver);
+
+        JackrabbitSession session = Mockito.mock(JackrabbitSession.class);
+        Mockito.when(resolver.adaptTo(Mockito.any())).thenReturn(session);
+
+        ValueFactory vf = new AbstractValueFactory() {
+
+            @Override
+            protected void checkPathFormat(String pathValue) throws ValueFormatException {
+                // TODO Auto-generated method stub
+
+            }
+
+            @Override
+            protected void checkNameFormat(String nameValue) throws ValueFormatException {
+                // TODO Auto-generated method stub
+
+            }
+
+        };
+        Mockito.when(session.getValueFactory()).thenReturn(vf);
+
+        UserManager userManager = Mockito.mock(UserManager.class);
+        Mockito.when(session.getUserManager()).thenReturn(userManager);
+
+        User validUser = Mockito.mock(User.class);
+        Mockito.when(validUser.getProperty(FormConstants.PN_RESETTOKEN))
+                .thenReturn(new Value[] { new StringValue("123") });
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.HOUR, 24);
+        Mockito.when(validUser.getProperty(FormConstants.PN_RESETTIMEOUT))
+                .thenReturn(new Value[] { new DateValue(cal) });
+        Mockito.when(userManager.getAuthorizable(Mockito.eq("valid@email.com"))).thenReturn(validUser);
+
+        User invalidUser = Mockito.mock(User.class);
+        Mockito.when(invalidUser.getProperty(FormConstants.PN_RESETTOKEN))
+                .thenReturn(new Value[] { new StringValue("456") });
+        Mockito.when(userManager.getAuthorizable(Mockito.eq("invalid@email.com"))).thenReturn(invalidUser);
+
+        User expiredUser = Mockito.mock(User.class);
+        Mockito.when(expiredUser.getProperty(FormConstants.PN_RESETTOKEN))
+                .thenReturn(new Value[] { new StringValue("456") });
+        cal = Calendar.getInstance();
+        cal.add(Calendar.HOUR, -24);
+        Mockito.when(expiredUser.getProperty(FormConstants.PN_RESETTIMEOUT))
+                .thenReturn(new Value[] { new DateValue(cal) });
+        Mockito.when(userManager.getAuthorizable(Mockito.eq("expired@email.com"))).thenReturn(expiredUser);
+
+    }
+
+    @Test
+    public void testHandleForm() throws FormException {
+
+        RequestPasswordResetAction action = new RequestPasswordResetAction(factory, null);
+
+        FormRequest request = new FormRequestImpl(new MockSlingHttpServletRequest(resolver), null, null);
+        request.getFormData().put("email", "test@email.com");
+        request.getFormData().put(FormConstants.PN_RESETTOKEN, "123");
+        request.getFormData().put(FormConstants.PN_PASSWORD, "password1");
+
+        Resource actionResource = new MockResource("/content", Collections.emptyMap(), null);
+
+        FormActionResult result = action.handleForm(actionResource, request);
+        assertTrue(result.isSucceeded());
+
+    }
+
+    @Test
+    public void testNoUser() throws FormException {
+
+        ResetPasswordAction action = new ResetPasswordAction(factory, null);
+
+        FormRequest request = new FormRequestImpl(new MockSlingHttpServletRequest(resolver), null, null);
+        request.getFormData().put("email", "test1@email.com");
+
+        Resource actionResource = new MockResource("/content",
+                Collections.singletonMap(RequestPasswordResetAction.PN_RESETTIMEOUT, 2), null);
+
+        FormActionResult result = action.handleForm(actionResource, request);
+        assertFalse(result.isSucceeded());
+
+    }
+
+    @Test
+    public void testHandles() throws FormException {
+        Config config = new Config() {
+            @Override
+            public Class<? extends Annotation> annotationType() {
+                return null;
+            }
+
+            @Override
+            public String[] supportedTypes() {
+                return new String[] { RequestPasswordResetAction.DEFAULT_RESOURCE_TYPE };
+            }
+        };
+        RequestPasswordResetAction action = new RequestPasswordResetAction(null, config);
+        Resource validResource = Mockito.mock(Resource.class);
+        Mockito.when(validResource.getResourceType()).thenReturn(RequestPasswordResetAction.DEFAULT_RESOURCE_TYPE);
+        assertTrue(action.handles(validResource));
+
+        Resource inValidResource = Mockito.mock(Resource.class);
+        Mockito.when(inValidResource.getResourceType()).thenReturn("something/else");
+        assertFalse(action.handles(inValidResource));
+    }
+
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailActionTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailActionTest.java
index eb4aaf7..815dd5e 100644
--- a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailActionTest.java
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailActionTest.java
@@ -19,6 +19,8 @@ package org.apache.sling.cms.reference.forms.impl.actions;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import java.lang.annotation.Annotation;
+
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.cms.reference.forms.FormActionResult;
 import org.apache.sling.cms.reference.forms.FormException;
@@ -47,12 +49,20 @@ public class SendEmailActionTest {
         context.request().setResource(context.resourceResolver().getResource("/form/jcr:content/container/form"));
 
         resolver = context.resourceResolver();
-        action = new SendEmailAction();
-
         mailService = Mockito.mock(MailService.class);
         Mockito.when(mailService.getMessageBuilder()).thenReturn(new MockMessageBuilder());
-
-        action.setMailService(mailService);
+        action = new SendEmailAction(mailService, new SendEmailAction.Config() {
+
+            @Override
+            public Class<? extends Annotation> annotationType() {
+                return null;
+            }
+
+            @Override
+            public String[] supportedTypes() {
+                return new String[] { SendEmailAction.DEFAULT_RESOURCE_TYPE };
+            }
+        });
     }
 
     @Test
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateResourceActionTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateResourceActionTest.java
new file mode 100644
index 0000000..49315cb
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateResourceActionTest.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.reference.forms.impl.actions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.HashMap;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ModifiableValueMapDecorator;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class UpdateResourceActionTest {
+
+    @Test
+    public void testHandles() {
+
+        UpdateResourceAction ur = new UpdateResourceAction(null);
+
+        Resource validResource = Mockito.mock(Resource.class);
+        Mockito.when(validResource.getResourceType()).thenReturn("reference/components/forms/actions/updateresource");
+        assertTrue(ur.handles(validResource));
+
+        Resource invalidResource = Mockito.mock(Resource.class);
+        Mockito.when(invalidResource.getResourceType())
+                .thenReturn("reference/components/forms/actions/someotheraction");
+        assertFalse(ur.handles(invalidResource));
+    }
+
+    @Test
+    public void testActionResolver() throws FormException {
+
+        UpdateResourceAction ur = new UpdateResourceAction(null);
+
+        ModifiableValueMap contentData = new ModifiableValueMapDecorator(new HashMap<String, Object>());
+        contentData.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+        Resource contentResource = Mockito.mock(Resource.class);
+        Mockito.when(contentResource.getValueMap()).thenReturn(contentData);
+        Mockito.when(contentResource.adaptTo(Mockito.any())).thenReturn(contentData);
+
+        ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        Mockito.when(resolver.getResource(Mockito.eq("/content/test"))).thenReturn(contentResource);
+
+        ValueMap actionData = new ModifiableValueMapDecorator(new HashMap<String, Object>());
+        actionData.put("allowedProperties", new String[] { "name1", "name3", "name4" });
+        actionData.put("path", "/content/test");
+
+        Resource actionResource = Mockito.mock(Resource.class);
+        Mockito.when(actionResource.getValueMap()).thenReturn(actionData);
+        Mockito.when(actionResource.getResourceResolver()).thenReturn(resolver);
+
+        FormRequest formRequest = Mockito.mock(FormRequest.class);
+        ValueMap formData = new ValueMapDecorator(new HashMap<>());
+        formData.put("name2", "value2");
+        formData.put("name3", "value3");
+        Mockito.when(formRequest.getFormData()).thenReturn(formData);
+
+        ur.handleForm(actionResource, formRequest);
+
+        assertEquals(2, contentResource.getValueMap().keySet().size());
+        assertEquals("value3", contentResource.getValueMap().get("name3"));
+        assertFalse(contentResource.getValueMap().containsKey("name2"));
+
+    }
+
+    @Test
+    public void testInvalidServiceUser() throws FormException, LoginException {
+
+        LoginException le = new LoginException();
+        ResourceResolverFactory factory = Mockito.mock(ResourceResolverFactory.class);
+        Mockito.when(factory.getServiceResourceResolver(Mockito.anyMap())).thenThrow(le);
+
+        UpdateResourceAction ur = new UpdateResourceAction(factory);
+
+        ValueMap actionData = new ModifiableValueMapDecorator(new HashMap<String, Object>());
+        actionData.put("allowedProperties", new String[] { "name1", "name3", "name4" });
+        actionData.put("path", "/content/test");
+        actionData.put("serviceUser", "bob");
+
+        Resource actionResource = Mockito.mock(Resource.class);
+        Mockito.when(actionResource.getValueMap()).thenReturn(actionData);
+
+        FormRequest formRequest = Mockito.mock(FormRequest.class);
+        ValueMap formData = new ValueMapDecorator(new HashMap<>());
+        formData.put("name2", "value2");
+        formData.put("name3", "value3");
+        Mockito.when(formRequest.getFormData()).thenReturn(formData);
+
+        try {
+            ur.handleForm(actionResource, formRequest);
+            fail();
+        } catch (FormException fe) {
+            assertEquals(le, fe.getCause());
+        }
+    }
+
+    @Test
+    public void testServiceUser() throws FormException, LoginException {
+
+        ModifiableValueMap contentData = new ModifiableValueMapDecorator(new HashMap<String, Object>());
+        contentData.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+        Resource contentResource = Mockito.mock(Resource.class);
+        Mockito.when(contentResource.getValueMap()).thenReturn(contentData);
+        Mockito.when(contentResource.adaptTo(Mockito.any())).thenReturn(contentData);
+
+        ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        Mockito.when(resolver.getResource(Mockito.eq("/content/test"))).thenReturn(contentResource);
+
+        ResourceResolverFactory factory = Mockito.mock(ResourceResolverFactory.class);
+        Mockito.when(factory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resolver);
+
+        UpdateResourceAction ur = new UpdateResourceAction(factory);
+
+        ValueMap actionData = new ModifiableValueMapDecorator(new HashMap<String, Object>());
+        actionData.put("allowedProperties", new String[] { "name1", "name3", "name4" });
+        actionData.put("path", "/content/test");
+        actionData.put("serviceUser", "bob");
+
+        Resource actionResource = Mockito.mock(Resource.class);
+        Mockito.when(actionResource.getValueMap()).thenReturn(actionData);
+
+        FormRequest formRequest = Mockito.mock(FormRequest.class);
+        ValueMap formData = new ValueMapDecorator(new HashMap<>());
+        formData.put("name2", "value2");
+        formData.put("name3", "value3");
+        Mockito.when(formRequest.getFormData()).thenReturn(formData);
+
+        ur.handleForm(actionResource, formRequest);
+
+        assertEquals(2, contentResource.getValueMap().keySet().size());
+        assertEquals("value3", contentResource.getValueMap().get("name3"));
+        assertFalse(contentResource.getValueMap().containsKey("name2"));
+
+    }
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/HoneypotHandlerTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/HoneypotHandlerTest.java
new file mode 100644
index 0000000..7deb692
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/HoneypotHandlerTest.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.reference.forms.impl.fields;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.impl.SlingContextHelper;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class HoneypotHandlerTest {
+
+    @Rule
+    public final SlingContext context = new SlingContext();
+    private HoneypotHandler handler;
+    private ResourceResolver resolver;
+
+    @Before
+    public void init() {
+        SlingContextHelper.initContext(context);
+
+        resolver = context.resourceResolver();
+        handler = new HoneypotHandler();
+    }
+
+    @Test
+    public void testHandles() {
+
+        assertFalse(handler.handles(resolver.getResource("/form/jcr:content/container/form/fields/fieldset/fields")));
+
+        assertTrue(handler
+                .handles(resolver.getResource("/form/jcr:content/container/form/fields/fieldset/fields/honeypot")));
+    }
+
+    @Test
+    public void testValid() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().setParameterMap(Collections.singletonMap("someothervalue", "something else"));
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/honeypot");
+
+        handler.handleField(context.request(), fieldResource, formData);
+
+        assertTrue(formData.isEmpty());
+    }
+
+    @Test
+    public void testInValid() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().setParameterMap(Collections.singletonMap("honeypot", "a value"));
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/honeypot");
+
+        try {
+
+            handler.handleField(context.request(), fieldResource, formData);
+            fail();
+        } catch (FormException e) {
+            // expected
+        }
+
+        assertTrue(formData.isEmpty());
+    }
+
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/TextfieldHandlerTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/TextfieldHandlerTest.java
index 3f1afcd..855127f 100644
--- a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/TextfieldHandlerTest.java
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/TextfieldHandlerTest.java
@@ -18,10 +18,13 @@ package org.apache.sling.cms.reference.forms.impl.fields;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.io.ByteArrayInputStream;
 import java.util.Calendar;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -71,12 +74,21 @@ public class TextfieldHandlerTest {
             handler.handleField(context.request(), fieldResource, formData);
             fail();
         } catch (FormException pe) {
+            // expected
+        }
+
+        context.request()
+                .setParameterMap(ImmutableMap.<String, Object>builder().put("invaliddate", "2019-99-99").build());
+
+        Resource invalidType = resolver.getResource("/form/jcr:content/container/invalidtype");
+        try {
+            handler.handleField(context.request(), invalidType, formData);
+            fail();
+        } catch (FormException pe) {
 
         }
 
         Resource invalidDate = resolver.getResource("/form/jcr:content/container/invaliddate");
-        context.request()
-                .setParameterMap(ImmutableMap.<String, Object>builder().put("invalidate", "2019-02-12").build());
         try {
             handler.handleField(context.request(), invalidDate, formData);
             fail();
@@ -103,9 +115,30 @@ public class TextfieldHandlerTest {
             handler.handleField(context.request(), fieldResource, formData);
             fail();
         } catch (FormException pe) {
+            // expected
+        }
 
+        fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/unvalidateddouble");
+        try {
+            handler.handleField(context.request(), fieldResource, formData);
+            fail();
+        } catch (FormException pe) {
+            // expected
         }
+    }
 
+    @Test
+    public void testFile() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().setParameterMap(Collections.singletonMap("file", new ByteArrayInputStream(new byte[0])));
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver.getResource("/form/jcr:content/container/form/fields/fieldset/fields/file");
+        handler.handleField(context.request(), fieldResource, formData);
+        assertNotNull(formData.get("file"));
+        assertEquals(ByteArrayInputStream.class, formData.get("file").getClass());
     }
 
     @Test
@@ -137,9 +170,17 @@ public class TextfieldHandlerTest {
             handler.handleField(context.request(), fieldResource, formData);
             fail();
         } catch (FormException pe) {
-
+            // expected
         }
 
+        fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/unvalidatedinteger");
+        try {
+            handler.handleField(context.request(), fieldResource, formData);
+            fail();
+        } catch (FormException pe) {
+
+        }
     }
 
     @Test
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/providers/RequestParametersValueProviderTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/providers/RequestParametersValueProviderTest.java
new file mode 100644
index 0000000..eeb771c
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/providers/RequestParametersValueProviderTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.reference.forms.impl.providers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.servlethelpers.MockSlingHttpServletRequest;
+import org.apache.sling.testing.resourceresolver.MockResource;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class RequestParametersValueProviderTest {
+
+    @Test
+    public void testAccepts() {
+        RequestParametersValueProvider rpp = new RequestParametersValueProvider();
+
+        Resource validResource = Mockito.mock(Resource.class);
+        Mockito.when(validResource.getResourceType())
+                .thenReturn("reference/components/forms/providers/requestparameters");
+        assertTrue(rpp.handles(validResource));
+
+        Resource invalidResource = Mockito.mock(Resource.class);
+        Mockito.when(invalidResource.getResourceType())
+                .thenReturn("reference/components/forms/providers/someotherprovider");
+        assertFalse(rpp.handles(invalidResource));
+    }
+
+    @Test
+    public void testRequestParameters() {
+        RequestParametersValueProvider rpp = new RequestParametersValueProvider();
+
+        MockResource mockResource = new MockResource("/apps/rpv",
+                Collections.singletonMap("allowedParameters", new String[] { "name1", "name3", "name4" }), null);
+
+        MockSlingHttpServletRequest mockRequest = new MockSlingHttpServletRequest(null);
+        mockRequest.addRequestParameter("name1", "value1");
+        mockRequest.addRequestParameter("name2", "value2");
+        mockRequest.addRequestParameter("name4", "value1");
+        mockRequest.addRequestParameter("name4", "value2");
+
+        Map<String, Object> formData = new HashMap<>();
+        rpp.loadValues(mockRequest, mockResource, formData);
+
+        assertTrue(formData.containsKey("name1"));
+        assertEquals("value1", formData.get("name1"));
+
+        assertFalse(formData.containsKey("name2"));
+        assertFalse(formData.containsKey("name3"));
+
+        assertTrue(formData.containsKey("name1"));
+        assertTrue(Arrays.equals(new String[] { "value1", "value2" }, (String[]) formData.get("name4")));
+    }
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/providers/SuffixResourceFormValueProviderTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/providers/SuffixResourceFormValueProviderTest.java
new file mode 100644
index 0000000..ab61a41
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/providers/SuffixResourceFormValueProviderTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.reference.forms.impl.providers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.testing.resourceresolver.MockResource;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class SuffixResourceFormValueProviderTest {
+
+    public Resource providerResource;
+
+    @Before
+    public void init() {
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("basePath", "/content/apath");
+        properties.put("allowedProperties", new String[] { "name1", "name3", "name4" });
+
+        providerResource = new MockResource("/apps/rpv", properties, null);
+
+    }
+
+    @Test
+    public void testAccepts() {
+        SuffixResourceFormValueProvider sr = new SuffixResourceFormValueProvider();
+
+        Resource validResource = Mockito.mock(Resource.class);
+        Mockito.when(validResource.getResourceType()).thenReturn("reference/components/forms/providers/suffixresource");
+        assertTrue(sr.handles(validResource));
+
+        Resource invalidResource = Mockito.mock(Resource.class);
+        Mockito.when(invalidResource.getResourceType())
+                .thenReturn("reference/components/forms/providers/someotherprovider");
+        assertFalse(sr.handles(invalidResource));
+    }
+
+    @Test
+    public void testNoSuffix() {
+        SuffixResourceFormValueProvider sr = new SuffixResourceFormValueProvider();
+
+        Map<String, Object> formData = new HashMap<>();
+
+        SlingHttpServletRequest mockRequest = Mockito.mock(SlingHttpServletRequest.class);
+        RequestPathInfo rpi = Mockito.mock(RequestPathInfo.class);
+        Mockito.when(mockRequest.getRequestPathInfo()).thenReturn(rpi);
+        sr.loadValues(mockRequest, providerResource, formData);
+
+        assertEquals(0, formData.entrySet().size());
+        Mockito.verify(rpi).getSuffixResource();
+
+    }
+
+    @Test
+    public void testValidSuffix() {
+        SuffixResourceFormValueProvider sr = new SuffixResourceFormValueProvider();
+
+        Map<String, Object> formData = new HashMap<>();
+
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("name1", "value1");
+        properties.put("name2", "value2");
+
+        Resource suffixResource = new MockResource("/content/apath/apage", properties, null);
+
+        SlingHttpServletRequest mockRequest = Mockito.mock(SlingHttpServletRequest.class);
+        RequestPathInfo rpi = Mockito.mock(RequestPathInfo.class);
+        Mockito.when(rpi.getSuffixResource()).thenReturn(suffixResource);
+        Mockito.when(mockRequest.getRequestPathInfo()).thenReturn(rpi);
+        sr.loadValues(mockRequest, providerResource, formData);
+
+        assertEquals(2, formData.entrySet().size());
+        assertEquals("value1", formData.get("name1"));
+        assertEquals("/content/apath/apage", formData.get("suffixResource"));
+        Mockito.verify(rpi).getSuffixResource();
+
+    }
+
+    @Test
+    public void testAllAllowed() {
+        SuffixResourceFormValueProvider sr = new SuffixResourceFormValueProvider();
+
+        Map<String, Object> formData = new HashMap<>();
+
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("name1", "value1");
+        properties.put("name2", "value2");
+
+        Map<String, Object> providerProperties = new HashMap<>();
+        providerProperties.put("basePath", "/content/apath");
+
+        Resource pr2 = new MockResource("/apps/rpv", providerProperties, null);
+
+        Resource suffixResource = new MockResource("/content/apath/apage", properties, null);
+
+        SlingHttpServletRequest mockRequest = Mockito.mock(SlingHttpServletRequest.class);
+        RequestPathInfo rpi = Mockito.mock(RequestPathInfo.class);
+        Mockito.when(rpi.getSuffixResource()).thenReturn(suffixResource);
+        Mockito.when(mockRequest.getRequestPathInfo()).thenReturn(rpi);
+        sr.loadValues(mockRequest, pr2, formData);
+
+        assertEquals(3, formData.entrySet().size());
+        assertEquals("value1", formData.get("name1"));
+        assertEquals("value2", formData.get("name2"));
+        assertEquals("/content/apath/apage", formData.get("suffixResource"));
+        Mockito.verify(rpi).getSuffixResource();
+
+    }
+
+    @Test
+    public void testInvalidSuffix() {
+        SuffixResourceFormValueProvider sr = new SuffixResourceFormValueProvider();
+
+        Map<String, Object> formData = new HashMap<>();
+
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("name1", "value1");
+        properties.put("name2", "value2");
+
+        Resource suffixResource = new MockResource("/content/anotherpath", properties, null);
+
+        SlingHttpServletRequest mockRequest = Mockito.mock(SlingHttpServletRequest.class);
+        RequestPathInfo rpi = Mockito.mock(RequestPathInfo.class);
+        Mockito.when(rpi.getSuffixResource()).thenReturn(suffixResource);
+        Mockito.when(mockRequest.getRequestPathInfo()).thenReturn(rpi);
+        sr.loadValues(mockRequest, providerResource, formData);
+
+        assertEquals(0, formData.entrySet().size());
+        Mockito.verify(rpi).getSuffixResource();
+
+    }
+}
diff --git a/reference/src/test/resources/form.json b/reference/src/test/resources/form.json
index dbe3595..1d558c8 100644
--- a/reference/src/test/resources/form.json
+++ b/reference/src/test/resources/form.json
@@ -8,11 +8,18 @@
         "published": true,
         "container": {
             "jcr:primaryType": "nt:unstructured",
+            "invalidtype": {
+                "jcr:primaryType": "nt:unstructured",
+                "required": true,
+                "name": "invalidtype",
+                "label": "Daty",
+                "saveAs": "date",
+                "sling:resourceType": "reference/components/forms/fields/textfield"
+            },
             "invaliddate": {
                 "jcr:primaryType": "nt:unstructured",
                 "required": true,
-                "name": "datefield",
-                "type": "text",
+                "name": "invaliddate",
                 "label": "Daty",
                 "saveAs": "date",
                 "sling:resourceType": "reference/components/forms/fields/textfield"
@@ -101,6 +108,23 @@
                                 "saveAs": "double",
                                 "sling:resourceType": "reference/components/forms/fields/textfield"
                             },
+                            "unvalidateddouble": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "required": true,
+                                "name": "double",
+                                "type": "text",
+                                "label": "Daty",
+                                "saveAs": "double",
+                                "sling:resourceType": "reference/components/forms/fields/textfield"
+                            },
+                            "file": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "required": true,
+                                "name": "file",
+                                "type": "file",
+                                "label": "File",
+                                "sling:resourceType": "reference/components/forms/fields/textfield"
+                            },
                             "integer": {
                                 "jcr:primaryType": "nt:unstructured",
                                 "required": true,
@@ -110,6 +134,15 @@
                                 "saveAs": "integer",
                                 "sling:resourceType": "reference/components/forms/fields/textfield"
                             },
+                            "unvalidatedinteger": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "required": true,
+                                "name": "integer",
+                                "type": "text",
+                                "label": "Inty",
+                                "saveAs": "integer",
+                                "sling:resourceType": "reference/components/forms/fields/textfield"
+                            },
                             "datefield": {
                                 "jcr:primaryType": "nt:unstructured",
                                 "required": true,
@@ -130,6 +163,11 @@
                                 "multiple": false,
                                 "sling:resourceType": "reference/components/forms/fields/selection",
                                 "display": "select"
+                            },
+                            "honeypot": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "name": "honeypot",
+                                "sling:resourceType": "reference/components/forms/fields/honeypot"
                             }
                         }
                     }