Search code examples
javaservletsaemsling

Post request on sling servlet not working on publish instance


Yesterday, I faced a problem in writing sling post servlet in AEM 6.3, registered with resource type property. Actually my code works on author instance but it does not work on publish instance. The code below create a node and property on content path of the project with user given data. (Ignore import statements and semicolon in following code, it is written in groovy)

I'm not sure, is it a good practice to resolve resource by using instance of SlingHttpServletRequest and getting the session?

And also, I'm able to find any difference between session.save() or resolver.commit.

Can anyone please help?

@SlingServlet(
    resourceTypes = ["app/project/components/formComp"],
    extensions = ['json'],
    methods = "POST")
@Properties([
    @Property(name = "Code ABC", value = 'Project ABC'),
    @Property(name = "Description ABC", value = 'servlet form')])
@CompileStatic
@Slf4j
class PostFormServlet extends SlingAllMethodsServlet {

    ResourceResolver resolver
    Session session

    @Override
    void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) {

        String myNodePath = 'content/project/en/mynode'
        String requestParam = 'param'
        try {
            resolver = request.getResourceResolver()
            session = resolver.adaptTo(Session)
            Node root = session.getRootNode()

            Node myDestinationNode
            if (rootNode.hasNode(myNodePath)) {
                myDestinationNode = rootNode.getNode(myNodePath)
            } else {
                myDestinationNode = rootNode.addNode(myNodePath, NT_UNSTRUCTURED)
            }
            String paramValue = request.getParameter(requestParam)

            if (myDestinationNode) {
                Date date = new Date();
                timeStamp = date.getTime() as String
                Node dateTimeNode = myDestinationNode.addNode(timeStamp, NT_UNSTRUCTURED)
                if (dateTimeNode) {
                    dateTimeNode.setProperty(requestParam, paramValue)
                }
            }
            session.save()
        } catch (Exception ex) {
           //log error
        }


        response.contentType = 'application/json'
        response.getWriter().write("Node Created")
    }
}

Solution

  • Your Servlet works, if the POST request is made as admin-user. So the script-resolution works fine, and it is a question of permissions.

    The permissions are checked at several layers:

    Permission Check - Dispatcher: The Dispatcher may disallow POST-requests for certain paths, or requires some kind of authentication, or simply removes some query parameter or other payload from the request. But it is easy to identify, if an error messages comes from the Apache or from the Publisher. Otherwise you can send the POST request once via the Dispatcher and once directly to the Publisher. If the result differs, then you have to take care for the Dispatcher first.

    Permission Check - Apache Sling Authentication Service: This is mainly relevant for the Author, as the Publisher is pretty open by default. This Service simply says, which paths can be accessed as Anonymous (meaning without any authentication), and for which paths a user is forwarded to a login-page. This service can be configured via OSGi, or by specifying a sling.auth.requirements property directly at your Servlet. The Authentication Service will read such properties, and treats that as configuration for itself. As said before, it is mainly relevant for the Author - as by default only the Login-Page is accessible without authentication.

    Permission Check - Content Resource: If you register your Servlet via an SlingResourceType, then the Script-Resolution Process requires Read-Permissions on the requested resource. At the Publisher the /content/...-tree is normally readable for Anonymous, but not something like /app/.... You can easily avoid this, if you register your Servlet on a path. For such servlets, there is no Sling permissions check (good or bad, depends on what you want).


    Permissions of the Servlet

    The above permission checks are relevant, that your Servlet is called. But if called, the Servlet still has only the read and write-permissions of the calling user. Which is for Anonymous very, very little (reading /content/..., no write permissions).

    So you need to open a new session with a service user.

    public class TestServlet extends SlingAllMethodsServlet {
    
        @Reference
        private ResourceResolverFactory resolverFactory;
    
        @Override
        protected void doPost(@Nonnull SlingHttpServletRequest request, @Nonnull SlingHttpServletResponse response) throws ServletException, IOException {
    
            final Map<String, Object> authenticationInfo = Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "testservlet");
            try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(authenticationInfo)) {
    
                Resource rootRes = resolver.getResource("/content/....");
                resolver.create(rootRes, "test", null);
                resolver.commit();
    
            } catch (Exception e) {
                response.setContentType("text/plain");
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                e.printStackTrace(response.getWriter());
            }
        }
    }
    

    At the beginning, it will say something like org.apache.sling.api.resource.LoginException: Cannot derive user name for bundle ....

    You need to:

    1. Create a system user (normal users will NOT work!)
    2. Add a service user mapping (OSGi-config)
    3. Grant read/write permission to your system user

    Either follow these instructions:

    https://helpx.adobe.com/experience-manager/6-3/sites/administering/using/security-service-users.html

    Or use a tool to create service-users and grant permissions for them. Either you already use one, or I recommend https://github.com/Netcentric/accesscontroltool. The mapping still requires an OSGi config.


    If the service user is too complicated for a first trial, you can still use the deprecated ResourceResolver resolver = resolverFactory.getAdministrativeResourceResolver(null). It is not secure, and therefore deprecated. You only have to whitelist your bundle via an OSGi config (Apache Sling Login Admin Whitelist - additional bundles)


    Final question :

    difference between session.save() or resolver.commit

    The resolver is the Sling-wrapper around the Jackrabbit-Oak session. So resolver.commit() is calling session.save() automatically (but NOT the other way around).

    It is strongly recommended to use the most high-level API you can - and NOT mixing high-level with low-level API's. Exceptions may occur - but not for beginners. (e.g. the PageManager-API is built on top of the Slings Resource-API, which is built on top of the Jackrabbit OAK's Node-API. The difficulty is to know, which API's exist)