Saturday, March 25, 2017

Integration of AEM with Salesforce - Part1


AEM Salesforce cloud connector can be used to integrate to Salesforce via connected App configured in the Salesforce

The AEM Salesfore integration supports the following functionalities:
  • Lead Search
  • Contact Search
  • Export AEM user as Salesforce Lead
  • Associate an AEM user with a Salesforce "Contact" or "Lead"
The connector can be extended to support the additional functionalities.


Defining the connected App in Salesforce:

A connected app integrates an application with Salesforce using APIs
Connected apps use standard SAML and OAuth protocols to authenticate and provide tokens for use with Salesforce APIs. The required security policies can be set to control the connected Apps access.

Login to Salesforce - login.salesforce.com
Click on the user name- Setup- Click on Create then Apps
Create New connected App
Enter the name and Email address


Click on Enable OAuth settings
Add the below two OAuth scopes - Add the additional scopes if required
  • Access and manage your data (api)
  • Perform requests on your behalf at any time (refresh_token, offline_access)
Specifiy the call back URL - https://<<AEM Host name>>:<<AEM Port name>>//etc/cloudservices/salesforce/<<AEM Salesforce Connector Name>>

e.g. https://localhost:5403/etc/cloudservices/salesforce/SalesforceConnect.html

The call back URL should be https
Click Save


Copy the Consumer Key and Consumer Secret


Click on Manage - Edit Policies
Select the following options
  • Relax IP restrictions
  • Refresh token is valid until revoked
Click on Save


AEM Configuration:

Open https;//<<AEM Server host>>:<<AEM Server port>>/etc/cloudservices/salesforce.html
e.g - https://localhost:5403/etc/cloudservices/salesforce.html
AEM server should be configured with https to enable the Salesforce integration.

Add new Configuration


Enter the Title - Specify the Tile Sames as what ever provided in the callback URL.
Click on Create

Enter the Customer Key and Customer Secret Values


Click on Connect to Salesforce - Wait for 10 mins after creating the Connected App in Salesforce

If the connection is success the browser will redirect to Salesforce login page
Page will be displayed with permission to required access to connected App - Click on Allow


The popup will be displayed with successful message after success connection. Click ok on the popup and the setting window


The redirect_mismatch error will be displayed if the calback URL configured in the Salesforce and the redirect_uri send by the Connector is not maching


The popup with error "Error Getting Access Token" will be displayed if the server is not able to connect to Salesfore login URL- login.salesforce.com


Testing the integration: Use the "Salesforce.com Export workflow " to export the AEM user to Salesforce as Lead.
Edit the "Salesforce.com Export" workflow and select the Salesforce configuration


Enter all the mandatory field to the user profile

Run the workflow by selecting the required profile


The Lead will be created in Salesforce


This post is written based on AEM 6,1.




Friday, March 3, 2017

Time zone difference in Author/Publishers - Adobe CQ5/AEM

The timezone configured in the OS(Linux) level is CST but some time the log files displays the timezone in GMT

To fix the issue force the server to use the required timezone in startup file(start.sh)
e.g.
CQ_JVM_OPTS='-server -Xmx1024m -XX:MaxPermSize=256M -Duser.timezone=US/Central -Djava.awt.headless=true'




Thursday, March 2, 2017

How to protect the content from anonymous access through SAML based SSO - Adobe CQ5/AEM

How to enable SAML based SSO for publisher - Adobe CQ5/AEM
How to enable SAML based SSO in publisher to protect the content while accessing via dispatcher - Adobe CQ5/AEM

This post will explain the steps required to protect the published content from anonymous access through SAML based SSO while accessing via dispatcher/publisher - Adobe CQ5/AEM

Out of scope for this post - Configurations of IDP provider. Make sure the return URL configured in SAML provider is /saml_login

Enable Authentication for required content path:
Go to http://localhost:4503/system/console/configMgr(publisher)
Search for Apache Sling Authentication Service
Add the path that required the authentication to Authentication requirements in the following format +<<Content Path>> e.g. +/content/test


Configure the IDP certificate in AEM:
Go to http://localhost:4503/system/console/configMgr(publisher)
Under /etc/key in the repository, create a node called "saml"(type nt:folder).
Inside this node, add a new binary property called  "idp_cert" for the public certificate of the IdP.
Upload the certificate file by double clicking on idp_cert property
Save All


Go to: http://localhost:4503/libs/granite/security/content/useradmin.html(publisher)
Select any user because TrustStore is global to AEM
Create trust store by supplying the password & then manage trust store
Upload the IdP certificate & make note of the certificate Alias

Go to: http://localhost:4503/libs/granite/security/content/useradmin.html(publisher)
Select authentication-service
Create KeyStore by supplying the password
If encrypting SAML assertions then go to manage KeyStore for uploading the private & public key

Configure the user group for restricting the access to required content:
Go to http://localhost:4503/useradmin(publisher)
Create a new group(content-access-group) with read access to content(provide the read assess only to the specific folder under content -/content/test and the required dam folders) and etc folder


Configure SAML 2.0 Authentication Handler:

Go to http://localhost:4503/system/console/configMgr(publisher)
Search for Adobe Granite SAML 2.0 Authentication Handler
Provide the required details


IDP URL - URL of the IDP where the SAML Authentication Request should be sent to
Provide all the required values
Service Provider Entity ID - ID which uniquely identifies this service provider with the identity provider
UserID Attribute - The name of the attribute containing the user ID from IDP
IDP Certificate Alias - Provide the certificate alais created in the above step
Select Auto create CRX Users
Select Add to Groups
Specify content-access-group in Default Groups(group created in previous step)
Password of Key Store - Specify the key store password specified in the above step.

Configure Referrer Filter:
Go to http://localhost:4503/system/console/configMgr(publisher)
Serach for Apache Sling Referrer Filter
Configure IDP host at Allow Hosts


Access the content URL now - http://test.dispatcher.com/content/test/en.html
The user will be redirected to SAML provider and will be redirected to target page upon successful authentication.

All the user configured in SAML provider will be able to access the target page and the users will be auto created in AEM after successful authentication.
If we want to give the access only to predefined users in AEM then remove Auto create CRX Users and Add to Groups from Adobe Granite SAML 2.0 Authentication Handler configuration.

The authentication will not work if multiple publisher serve the request, as a solution we have to enable sticky session to make sure the browser sessions are directed to same dispatcher/publisher 



Wednesday, March 1, 2017

How to generate sitemap for multi site environments? - Adobe CQ5/AEM

How to generate sitemap for multi site environments? - Adobe CQ5/AEM

This post will explain how to generate the sitemap for different sites(home pages) in multi site environment

Factory servlet to generate the sitemap.xml:

import java.io.IOException;
import java.util.*;

import javax.servlet.ServletException;
import javax.xml.stream.*;

import org.apache.commons.lang3.time.FastDateFormat;
import org.apache.felix.scr.annotations.*;
import org.apache.sling.api.*;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.commons.Externalizer;
import com.day.cq.wcm.api.*;

@Component(metatype = true, label = "Site Map", description = "Site Map", configurationFactory = true)
@Service
@SuppressWarnings("serial")
@Properties({
@Property(name = "sling.servlet.resourceTypes", unbounded = PropertyUnbounded.ARRAY,
label = "Homepage Resource Type", description = "Sling Resource Type for Home Page component"),
@Property(name = "sling.servlet.selectors", value = "sitemap", propertyPrivate = true),
@Property(name = "sling.servlet.extensions", value = "xml", propertyPrivate = true),
@Property(name = "sling.servlet.methods", value = "GET", propertyPrivate = true),
@Property(name = "webconsole.configurationFactory.nameHint",
value = "Site Map on resource types: [{sling.servlet.resourceTypes}]") })
public final class SiteMapGeneratorServlet extends SlingSafeMethodsServlet {

private static final Logger LOG = LoggerFactory.getLogger(SiteMapGeneratorServlet.class);
private static final FastDateFormat DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd");
private static final boolean INCLUDE_LAST_MODIFIED_DEFAULT_VALUE = false;

@Property(boolValue = INCLUDE_LAST_MODIFIED_DEFAULT_VALUE, label = "Include Last Modified Date",
description = "If checked, last modified value will be shown in sitemap.")
private static final String INCLUDE_LAST_MODIFIED_PROPERTY = "include.lastmod";

private static final String SITEMAP_NAMESPACE = "http://www.sitemaps.org/schemas/sitemap/0.9";

@Reference
private Externalizer externalizer;

private boolean incLastModified;

@Activate
protected void activate(Map<String, Object> properties) {
this.incLastModified = PropertiesUtil.toBoolean(properties.get(INCLUDE_LAST_MODIFIED_PROPERTY),
INCLUDE_LAST_MODIFIED_DEFAULT_VALUE);
}

@Override
protected void doGet(SlingHttpServletRequest slingRequest, SlingHttpServletResponse slingResponse)
throws ServletException, IOException {

slingResponse.setContentType(slingRequest.getResponseContentType());
ResourceResolver resourceResolver = slingRequest.getResourceResolver();
PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
Page pageObj = pageManager.getContainingPage(slingRequest.getResource());

XMLOutputFactory outputFactory = XMLOutputFactory.newFactory();
try {
XMLStreamWriter stream = outputFactory.createXMLStreamWriter(slingResponse.getWriter());

stream.writeStartDocument("1.0");
stream.writeStartElement("", "urlset", SITEMAP_NAMESPACE);
stream.writeNamespace("", SITEMAP_NAMESPACE);

// Current page
writeXML(pageObj, stream, slingRequest);

for (Iterator<Page> children = pageObj.listChildren(new PageFilter(), true); children.hasNext();) {
Page childPage = (Page) children.next();
// If condition added to make sure the pages hidden in search in page properties do not show up in sitemap
if (null != childPage) {
if (!childPage.getProperties().containsKey("hideInSearch")
|| (childPage.getProperties().containsKey("hideInSearch")
&& childPage.getProperties().get("hideInSearch").equals("false"))
|| (childPage.getProperties().containsKey("hideInSearch")
&& childPage.getProperties().get("hideInSearch").equals("")))
writeXML(childPage, stream, slingRequest);
}
}

stream.writeEndElement();
stream.writeEndDocument();

} catch (XMLStreamException e) {
throw new IOException(e);
}
}

private void writeXML(Page pageObj, XMLStreamWriter xmlStream, SlingHttpServletRequest slingRequest)
throws XMLStreamException {
xmlStream.writeStartElement(SITEMAP_NAMESPACE, "url");

String protocolPort = "http";
if (slingRequest.isSecure())
protocolPort = "https";

String locPath = this.externalizer.absoluteLink(slingRequest, protocolPort,
String.format("%s.html", pageObj.getPath()));

writeXMLElement(xmlStream, "loc", locPath);

if (this.incLastModified) {
Calendar calendarObj = pageObj.getLastModified();
if (null != calendarObj) {
writeXMLElement(xmlStream, "lastmod", DATE_FORMAT.format(calendarObj));
}
}
xmlStream.writeEndElement();
}

private void writeXMLElement(final XMLStreamWriter xmlStream, final String elementName, final String xmlText)
throws XMLStreamException {
xmlStream.writeStartElement(SITEMAP_NAMESPACE, elementName);
xmlStream.writeCharacters(xmlText);
xmlStream.writeEndElement();
}

}

Create new servlet configuration from the factory through OSGI console by providing the following details

Home Page Resouce Type - add the Home page resource types that should be considered for generating sitemap.xml

Include Last Modified Date - If selected the last modified date of the page will be included as part of the sitemap.xml



Enable hideInSearch checkbox in all page properties


Select hideInSearch for those child pages should be excluded from siemap.xml

Access the sitemap.xml for the site with the following URL -  http://<<site host>>/<<parent node with configured resource type>>.sitemap.xml

e.g.
http://example1.com/en.sitemap.xml
http://example2.com/en.sitemap.xml

<?xml version="1.0"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://example1.com/en.html</loc>
<lastmod>2017-02-28</lastmod>
</url>
<url>
<loc>http://example1.com/en/test.html</loc>
<lastmod>2017-02-28</lastmod>
</url>
</urlset>



Wednesday, February 22, 2017

How to exclude the replication agent from manual replication actions - Adobe CQ5/AEM?

Exclude the replication agent from manual replication actions- Sometimes we may required to exclude the specific replication agents from manual action but only the actions through workflow or API.

Enable "Ignore Default" in Triggers tab of Agent configuration wizard.





Monday, February 20, 2017

How to display the dynamic popup in a page through GTM?

This post will explain how to display the dynamic popup in a page through GTM.

The assumption is the required JQuery scripts are included as part of the page.

Define the cookie type variable: - this will help us to display the popup for every new browser session(this step is not required if the popup should displayed every time visiting the page)

  • Click on Variable in left hand side menu.
  • Go to user defined variables and select new.
  • Select variable configuration as 1st party cookie 
  • Give cookie name as popUpDisplayed and check url decode cookie option.
  • Save the changes and give the name as popUpDisplayed.



Creation of Trigger/Timer:

  • Click on trigger on left hand side menu and choose trigger type as Timer.
  • In the Trigger configuration give the interval in milliseconds as required .(e.g:5000 millliseconds) - the delay to display the popup in the page
  • Enter the Trigger conditions as below:
                  urlpath contains /es/test.html
                  popUpDisplayed  does not equal true - this will make sure the popup is displayed once per                  browser session
  • Select this trigger fires on all timers.
  • Click on save and give the name accordingly(e.g:popuptimer)




Creation of Tag:

Click on tag on left hand side menu and choose tag type as customHTML.

In the HTML text area give the below content:
<!-- The Modal -->
<div id="myModal" class="modal">

<!-- Modal content -->
<div class="modal-content">
<span id="modal-close" class="close">&times;</span>
    test popup message
</div>
</div>
<script>
  $('#myModal').show();
  $('#myModal').parent().css('display','block');
  $('#myModal').parent().css('visibility','visible');
  $('#modal-close').click(function(){
  $('#myModal').hide();
  });
 document.cookie = "popUpDIsplayed=true";  
</script>

<style>
/* The Modal (background) */
.modal {
    display: none; /* Hidden by default */
    position: fixed; /* Stay in place */
    z-index: 1; /* Sit on top */
    padding-top: 100px; /* Location of the box */
    left: 0;
    top: 0;
    width: 100%; /* Full width */
    height: 100%; /* Full height */
    overflow: auto; /* Enable scroll if needed */
    background-color: rgb(0,0,0); /* Fallback color */
    background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
    background-image: url("paper.gif");/*change the background image */
  }

/* Modal Content */
.modal-content {
    background-color: #fefefe;
    margin: auto;
    padding: 20px;
    border: 1px solid #888;
    width: 60%;
}

/* The Close Button */
.close {
    color: #aaaaaa;
    float: right;
    font-size: 28px;
    font-weight: bold;
}

.close:hover,
.close:focus {
    color: #000;
    text-decoration: none;
    cursor: pointer;
}
</style>

Expand the advanced settings and check Enable custom tag firing schedule flag.Give the start date, time, end date and time as required. - This will help us to display the popup during purticular time
Click on save and give the name accordingly (e.g:popUpHtml).





Tuesday, February 7, 2017

How to monitor the Replication Queues through Java - Adobe CQ5/AEM

Monitoring the Replication Queues through Java - Adobe CQ5/AEM

This post will explain the approach to monitor the Replication Queues through java

Enable the Remote JMX in the server:

Add the following configurations as part of CQ_JVM_OPTS in AEM startup file (start.sh or start.bat)

-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=50055

e.g.

CQ_JVM_OPTS='-server -Xmx6144m -XX:MaxPermSize=512M -Djava.awt.headless=true -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=50055'

Restart the server.

Configuration.txt

ServerHost=xxxxxxxxxxx
ServerPort=50055
QueueNames=publish,publish_Dev
Environment=Development
EmailHost=xxxxxx
Email_To=xxxxxx
Email_From=xxxxxxx
QueueThreshold=15

GetReplicationQueueStatus.java

import java.io.*;
import java.util.*;
import javax.management.*;
import javax.management.remote.*;
import javax.mail.*;
import javax.mail.internet.*;

public class GetReplicationQueueStatus {
static JMXConnector m_connector=null;
public static void main(String[] args) {
FileInputStream in=null;
try
{
Properties configuration = new Properties();
in= new FileInputStream("Configuration.txt");
configuration.load(in);
String serverHost=configuration.getProperty("ServerHost");
String serverUrl = "service:jmx:rmi:///jndi/rmi://"+serverHost+":"+configuration.getProperty("ServerPort")+"/jmxrmi";
JMXServiceURL serviceURL = new JMXServiceURL(serverUrl);
m_connector =JMXConnectorFactory.newJMXConnector(serviceURL,null);
m_connector.connect();
MBeanServerConnection m_connection = m_connector.getMBeanServerConnection();
Set<ObjectName> queryResult =m_connection.queryNames(new ObjectName("com.adobe.granite.replication:type=agent,*"),null);
String queueNames=configuration.getProperty("QueueNames");
List<String> queueNameList = Arrays.asList(queueNames.split(","));
for(ObjectName objectName : queryResult) {
if(queueNameList.contains(objectName.getKeyProperty("id").replaceAll("\"", "")))
{
String  attrNames[] = 
                   { "Enabled",
                     "QueueNumEntries",
                     "QueueBlocked",
                     "QueuePaused"                      
                   };
AttributeList  attrList = m_connection.getAttributes(objectName, attrNames);                
           String isEnabled= ((Object) attrList.get(0)).toString().split("=")[1];
           String queueNumEntries= ((Object) attrList.get(1)).toString().split("=")[1];
           String isQueueBlocked= ((Object) attrList.get(2)).toString().split("=")[1];
           String isQueuePaused= ((Object) attrList.get(3)).toString().split("=")[1];
           System.out.println( "Values: " +isEnabled+" "+ queueNumEntries+" "+isQueueBlocked+" "+ isQueuePaused);
           String to=configuration.getProperty("Email_To");
           String from=configuration.getProperty("Email_From");
           String emailHost=configuration.getProperty("EmailHost");
           String queueThreshold=configuration.getProperty("QueueThreshold");
           String environment=configuration.getProperty("Environment");
           if(!isEnabled.trim().equals("true"))
           {
            String subject=environment+": Replication Queue "+"\""+objectName.getKeyProperty("id").replaceAll("\"", "")+"\""+" is Disabled";
            sendEmail(to,from, subject,subject+"\n"+"Server: "+serverHost,  emailHost);
           }else if(isQueueBlocked.trim().equals("true"))
           {
            String subject=environment+": Replication Queue "+"\""+objectName.getKeyProperty("id").replaceAll("\"", "")+"\""+" is Blocked";
            sendEmail(to,from, subject,subject+"\n"+"Server: "+serverHost,  emailHost);
           }else if(isQueuePaused.trim().equals("true"))
           {
            String subject=environment+": Replication Queue "+"\""+objectName.getKeyProperty("id").replaceAll("\"", "")+"\""+" is Paused";
            sendEmail(to,from, subject,subject+"\n"+"Server: "+serverHost,  emailHost);
           }else if(Integer.parseInt(queueNumEntries.trim())>Integer.parseInt(queueThreshold.trim()))
           {
            String subject=environment+": Replication Queue "+"\""+objectName.getKeyProperty("id").replaceAll("\"", "")+"\""+" is Queued with "+queueNumEntries+" Requests";
            sendEmail(to,from, subject,subject+"\n"+"Server: "+serverHost,  emailHost);
           }
               
}
                
}
}catch(Exception e)
{
e.printStackTrace();
}finally {
try {
if(m_connector!=null)
{
m_connector.close();
}
if(in!=null)
{
in.close();
}
}catch(Exception e)
{
}
}
}
static void sendEmail(String to,String from,String subject,String body,String host)
{
 Properties properties = System.getProperties();
     properties.setProperty("mail.smtp.host", host);
     Session session = Session.getDefaultInstance(properties);

     try {
          MimeMessage message = new MimeMessage(session);
        message.setFrom(new InternetAddress(from));
           message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
           message.setSubject(subject);         
           message.setText("Hi Team\n\n"+body+"\n\n"+"Regards\nAdmin");
           Transport.send(message);
           System.out.println("Email send successfully...");

     }catch (MessagingException mex) {
        mex.printStackTrace();
     }
}

}

Configure the data in Configuration.txt file accordingly.
Email will be triggered whenever the configured Queue is disabled, blocked, paused or the pending request is more than the configured threshold.

Executable jar file can be generated for the java class and scheduled for continuous monitoring.



Contact Form

Name

Email *

Message *