Author Archive

May 17, 2013

Page level selectors validation to prevent DoS Attacks

In the Adobe CQ security checklist located at https://dev.day.com/docs/en/cq/current/deploying/security_checklist.html, Denial of Service (DoS) Attacks prevention was briefly touched on. In this blog post I am going to give an example on how to best handle selectors validation at the CQ page level.

As mentioned in the security checklist, one of the most commonly seen Denial of Service attacks targeting CQ is by requesting a content page with unlimited number of URLs. Without selectors validation, these page requests are then usually cached, causing the disks to fill up very quickly and bring down the service.

Remember the Apache Sling script resolution? A script may have the following form (see http://sling.apache.org/documentation/the-sling-engine/url-to-script-resolution.html):

{selectorStringPath}.{requestExtension}.{scriptExtension}

Imagine I have a valid CQ page test.html, here are the few variations I can have for the same page:

test.xyz.html
test.abc.html
test.aaa.html
test.bbb.html
test.abcde.html

All these would be valid pages (if without checking the selectors) and would be cached in the cache layer if configured so.

The best way to prevent the above is to do a validation of the selectors at the page level. Sling API, specifically the RequestPathInfo, provides the getSelectors() method to get all the selectors from the requested URL. If you are not expecting any selectors being passed to your CQ page, you should make sure that slingRequest.getRequestPathInfo().getSelectors() yield an empty array. Otherwise, you should do a very strict comparison of the selectors array with what you’re expecting.

If there’s any unexpected selectors, you may choose to throw a 404 (Not Found) or other error status code so that the page does not get cached.

 

Note that the above would only prevent invalid requests from being cached. These requests, although not cached, would still be routed all the way to the CQ servers (another means of DoS attacks is to generate massive number of requests to a targeted server). So another highly recommended line of defense, is to set up very strict dispatcher filter rules so that invalid requests would not generate any traffic to the CQ servers.

 

9:31 PM Permalink
February 11, 2013

Digital Marketing Webinar

Be sure to attend the Digital Marketing webinar that will discuss Adobe CQ dispatcher caching strategies. See the following link for more information:

http://app.response.adobesystemsinc.com/e/es.aspx?s=1391&e=131990&elq=71157a507acc4e7c8c1aaf08d74220e7

 

To sign up for this webinar, click the following link:

https://adobeformscentral.com/?f=wOfpVd6l9yvZGF%2AQy-MExw

2:46 PM Permalink
November 21, 2012

How to add custom namespace in CRX

There are times when custom namespace is needed in a system for organization and management purposes. Without registering the namespace with CRX, properties with custom namespace would not be accepted. In this blog post I will talk about two ways of registering a namespace in CRX.

To illustrate, let’s take a look at the behavior of CRX without registering any namespace. Let me go ahead and enter a property that has namespace in it:

Upon saving, I would get the following error:

Now let’s proceed with registering the namespace. There are two ways of doing this:

Register namespace via CRX Console

  1. Namespace can be added via Node Type Administration in CRX Console.
  2. In the Node Type Administration window, click on “Namespaces” which is located at far right of the toolbar.
  3.  At the bottom of the Namespaces window, click on “New”.
  4. Enter the URI and the Namespace mapping and click Ok. And you should see the namespace added:
  5. Voila! It’s that easy. And now you can add the property again with the registered namespace:

 

Register custom namespace via CND file

  1. Namespace can also be registered via a CND file. The CND file can be deployed with any CRX packages (install folder, or via the package manager).
  2. Once the package is installed on CRX, any namespaces in CND files found inside the package would be registered automatically.
  3. Here’s the content of the CND file:
    <demo='demo'>
  4. That’s it! It’s nothing more than a mapping=uri pair.
9:31 PM Permalink
August 19, 2012

CQ Selector driven results page

In this article I would like to talk about CQ pages where content is based on the Sling selectors. Recently I have worked on a simple requirement for a customer project where a page would have a drop-down (or multiple) menu to filter certain types of content. In the demo below I have a simple set of Adobe products where I want to be able to filter by the product name and/or the product version. Let’s check out the demo first (focus on the selection and the URL changes):

Selector driven content demo

As you can see from the quick demo. Dropdown selection would trigger a URL redirection where content is filtered based on the sling selector. For example:

products.illustrator.all.html displays only Adobe Illustrator products, all versions.
products.illustrator.cs4.html displays only the Adobe Illustrator CS4 product.
products.photoshop.cs6.html displays only the Adobe Photoshop CS6 product.
…so on and so on…

And for demo purposes, we have created pages with a Product component with the following fields:

Now let’s get to see how the filtering works. Here I will provide code pertaining to the sling selector processing and query only.

Sling selector processing (Pagination also factored in):

    // page number should be request param not selector
    int pageNum = 0;
    if (request.getParameter("page") != null) {
        try{
            pageNum = new Integer(request.getParameter("page")).intValue();
        } catch(NumberFormatException e){}
    }
    if(pageNum  0){
        // if product and version present
        if (selectors.length == 2) {
            selectedProduct = selectors[0].trim();
            selectedVersion = selectors[1].trim();
        } else if (selectors.length == 1) {
            if (selectors[0].trim().equalsIgnoreCase("cs4")) selectedVersion = "cs4";    
            else if (selectors[0].trim().equalsIgnoreCase("cs5")) selectedVersion = "cs5";    
            else if (selectors[0].trim().equalsIgnoreCase("cs6")) selectedVersion = "cs6";     
            else if (productsMap.containsKey((String) selectors[0].trim())) {
                selectedProduct = selectors[0].trim();
            } else {
                selectedProduct = "all";
                selectedVersion = "all";
            }
        } else {
            selectedProduct = "all";
            selectedVersion = "all";
        }
    }

The above code would determine the product and the version selected by the user (since they are passed in the URL). And once we have the product, version determined, we can run the following query and display the results in tabular format just like in the demo:

 

    QueryBuilder builder = null;
    Map<String, Object> map = null;
    Query query = null;
    SearchResult result = null;
 
    map = new HashMap<String, Object>();
    map.put("path", "/content/demo/en/products");
    map.put("type","cq:Page");
 
    map.put("1_property","jcr:content/cq:template");
    map.put("1_property.value","/apps/demo/templates/productspage"); // query only productspage
    map.put("2_property","jcr:content/par_main/product/sling:resourceType"); // query for pages with product component
    map.put("2_property.value","demo/components/content/product");
 
    if (!selectedProduct.equals("all") && !selectedVersion.equals("all")) {
        map.put("3_property","jcr:content/par_main/product/category");
        map.put("3_property.value",selectedProduct);
        map.put("4_property","jcr:content/par_main/webinar/productVersion");
        map.put("4_property.value",selectedVersion);
 
    } else if (!selectedProduct.equals("all")) {
        map.put("3_property","jcr:content/par_main/product/category");
        map.put("3_property.value",selectedProduct);
 
    } else if (!selectedVersion.equals("all")) {
        map.put("3_property","jcr:content/par_main/product/productVersion");
        map.put("3_property.value",selectedVersion);
    }
    map.put("5_orderby","@jcr:content/par_main/product/category");
    map.put("5_orderby.sort","asc");
    map.put("6_orderby","@jcr:content/jcr:title");
    map.put("6_orderby.sort","asc");   
 
    builder = resource.getResourceResolver().adaptTo(QueryBuilder.class);
    query = builder.createQuery(PredicateGroup.create(map), currentNode.getSession());
    query.setStart(startIndex);
    query.setHitsPerPage(15);
    result = query.getResult();

And the above queries for pages that are using the product template, with a product component in it, and filter by either product category or the version if they are passed in.

Enjoy!

10:32 PM Permalink

How to integrate with other web services without creating OSGi packages

A lot of enterprise grade systems require some sort of integration with other services these days, to extend and bring in more features into the systems. In this blog post I would like to show a quick and easy way to integrate CQ5 with other web services. Examples below will be showing a RESTful webservice but other webservices like SOAP will take similar approach.

Webservices integration usually involves building a integration layer. A Java Servlet would definitely be a good candidate. But to avoid building / compiling / packaging / deploying such code into an OSGi container, one can easily write a JSP (on the fly) in CQ to handle the integration.

Create an integration layer page:

This integration page is basically to create an instance of a CQ Page so that the rest of the content pages can communicate to it (Things under /app are not exposed or cannot be called directly). And extra benefit about this CQ Page -> You can control who has access to it!

Note the above page is using “Demo – API Page Template”, and here is the mapping:

 

And apipage.jsp is nothing more than what you want to surface in a typical Servlet.  Here is snippet of the code:

if (request.getParameter("action") != null) {
    if (request.getParameter("action").equals("getSomething")) {
        Node demoNode = resourceResolver.getResource(INTEGRATION_DESIGN_NODEPATH).adaptTo(Node.class);
        response.setContentType("application/json");
        out.write(getSomething(demoNode));
 
    } else if (request.getParameter("action").equals("getCategories")) {
        Node demoNode = resourceResolver.getResource(INTEGRATION_DESIGN_NODEPATH).adaptTo(Node.class);
        response.setContentType("application/json");
        out.write(getCategories(demoNode));
 
    } else if (request.getParameter("action").equals("getFields") &amp;&amp;
            request.getParameter("param1") != null &amp;&amp;
            request.getParameter("param2") != null) {
        response.setContentType("application/json");
        out.write(getFields(
            request.getParameter("param1"),
            request.getParameter("param2"));
 
    } else {
        response.setContentType("application/json");
        out.write("{\"error\":\"error\"}");
    }
}

 

And derived from the above, we now have the following links to retrieve data:

http://_server_:_port_/content/integrationdemo/en/integration/integrationapi?action=getSomething

http://_server_:_port_/content/integrationdemo/en/integration/integrationapi?action=getCategories

http://_server_:_port_/content/integrationdemo/en/integration/integrationapi?action=getFields&param1=test&param2=data

 

The integration implementation will be per your requirements, but at a high level you can pretty much do everything-in-a-servlet inside a JSP. JSPs are Servlets after all. Here is a snippet of a http call (to a webservice) inside the same JSP:

 

static String getFields(String apiKey, String accessToken, String someJson) {
    HttpClient httpClient = new HttpClient();
    HttpClientParams httpClientParams = new HttpClientParams();
    DefaultHttpMethodRetryHandler defaultHttpMethodRetryHandler = new DefaultHttpMethodRetryHandler(0, false);
    httpClientParams.setParameter(HttpClientParams.RETRY_HANDLER, defaultHttpMethodRetryHandler);
    httpClient.setParams(httpClientParams);
 
    PostMethod post = new PostMethod(EXTERNAL_WEBSERVICE_API_URL+apiKey.trim()+"/URL_EXAMPLE/test?oauth_token="+accessToken.trim());
    post.addRequestHeader("Accept", "application/json");
    post.addRequestHeader("Content-Type", "application/json");
    post.setRequestBody(someJson);
 
    try {
        httpClient.executeMethod(post);
        String result = post.getResponseBodyAsString();
 
        if (result != null &amp;&amp; !result.equals("")) {
            JSONObject returnObj = new JSONObject(result);
            return returnObj.toString();
        } else {
            return "";
        }
    } catch (Exception ex) {
        return ex.getMessage();
    }   
}

 

And finally, as an option (if you want to integration the CQ authoring experience with an external webservice), you can expose the data retrieved inside a CQ Component dialog field, you will need to configure the widget to retrieve webservice data. Details are documented in:

http://dev.day.com/docs/en/cq/current/widgets-api/index.html?class=CQ.form.Selection

Here’s a quick example:

And the result:

 

Enjoy~

11:21 AM Permalink