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:29 UTC

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

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"
                             }
                         }
                     }