/*
* Copyright 2012 david gonzalez.
*
* Licensed 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 com.activecq.samples.clientcontext.impl;
import com.activecq.samples.clientcontext.ClientContextBuilder;
import com.activecq.samples.clientcontext.ClientContextStore;
import com.adobe.granite.xss.ProtectionContext;
import com.adobe.granite.xss.XSSFilter;
import com.day.cq.commons.Externalizer;
import com.day.cq.wcm.api.WCMMode;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.jcr.resource.JcrResourceConstants;
import org.osgi.framework.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@Component(
label = "Samples - Client Context Builder",
description = "Service to build out custom Client Contexts",
metatype = false,
immediate = true
)
@Properties({
@Property(
label = "Vendor",
name = Constants.SERVICE_VENDOR,
value = "ActiveCQ",
propertyPrivate = true
)
})
@Service
public class ClientContextBuilderImpl implements ClientContextBuilder {
private static final Logger log = LoggerFactory.getLogger(ClientContextBuilderImpl.class);
@Reference
private XSSFilter xss;
@Reference
private Externalizer externalizer;
@Reference
private SlingRepository slingRepository;
@Reference
private ResourceResolverFactory resourceResolverFactory;
@Override
public JSONObject getJSON(SlingHttpServletRequest request,
ClientContextStore store) throws JSONException, RepositoryException {
if (store.handleAnonymous() && isAnonymous(request)) {
log.debug("Get Anonymous JSON");
return store.getAnonymousJSON(request);
} else {
log.debug("Get User JSON");
return store.getJSON(request);
}
}
@Override
public JSONObject xssProtect(JSONObject json, String... whitelist) throws JSONException {
final List<String> keys = IteratorUtils.toList(json.keys());
final boolean useWhiteList = !ArrayUtils.isEmpty(whitelist);
log.debug("Use Whitelist: " + !ArrayUtils.isEmpty(whitelist));
for (final String key : keys) {
log.debug("XSS Key: " + key);
if (!useWhiteList || (useWhiteList && !ArrayUtils.contains(whitelist, key))) {
log.debug("XSS -> " + key + XSS_SUFFIX + ": " + xss.filter(ProtectionContext.PLAIN_HTML_CONTENT, json.optString(key)));
json.put(key + XSS_SUFFIX, xss.filter(ProtectionContext.PLAIN_HTML_CONTENT, json.optString(key)));
}
}
log.debug("XSS JSON: " + json.toString(4));
return json;
}
@Override
public String getInitJavaScript(JSONObject json, ClientContextStore store) {
return getInitJavaScript(json, store.getContextStoreManagerName());
}
@Override
public String getInitJavaScript(JSONObject json, String manager) {
String script = "";
Iterator<String> keys = json.keys();
while (keys.hasNext()) {
final String key = keys.next();
script += getAddInitProperty(manager, key, json.optString(key));
}
return wrapWithAnonymousScope(script);
}
@Override
public boolean isSystemProperty(String key) {
return (!key.startsWith("jcr:") && !key.startsWith("sling:") && !key.startsWith("cq:last"));
}
@Override
public String getGenericInitJS(SlingHttpServletRequest request, ClientContextStore store) throws JSONException, RepositoryException {
final String manager = store.getContextStoreManagerName();
final String json = getJSON(request, store).toString();
final String script =
wrapWithManagerCheck("CQ_Analytics." + manager + ".loadInitProperties(" + json + ", true);", manager);
return wrapWithAnonymousScope(script);
}
@Override
public String getAuthorizableId(SlingHttpServletRequest request) {
if (StringUtils.isBlank(request.getQueryString())) {
// If the request does not have Query Params no matter what.
// We do not want to have a chance of caching non-anonymous personalized content.
log.debug("QP is blank; Is Anonymous");
return ANONYMOUS;
}
final AuthorizableResolution authorizableResolution = getAuthorizableResolution(request);
final WCMMode wcmMode = WCMMode.fromRequest(request);
if (wcmMode == null || WCMMode.DISABLED.equals(wcmMode)) {
// Publish mode
log.debug("Publish WCM Mode");
// Always look at the user Sling has associated with the Request in Publish mode
final ResourceResolver resourceResolver = request.getResourceResolver();
// TODO: better way to get AuthorizableId?
final String userId = resourceResolver.getUserID();
if (resourceResolver == null || StringUtils.equals(ANONYMOUS, userId)) {
log.debug("Is Anonymous");
return ANONYMOUS;
} else {
log.debug("Is User: " + userId);
return userId;
}
} else {
// Author mode; Allow impersonations by author using the Clickstream Cloud
log.debug("Author WCM Mode");
if (AuthorizableResolution.IMPERSONATION.equals(authorizableResolution)) {
// Get the authorizableId from the Query Params
log.debug("Get authorizableId from QP");
return StringUtils.strip(getParameterOrAttribute(request, AUTHORIZABLE_ID, null));
} else if (AuthorizableResolution.AUTHENTICATION.equals(authorizableResolution)) {
// Check the user Sling has associated with the request
final ResourceResolver resourceResolver = request.getResourceResolver();
final String userId = resourceResolver.getUserID();
if (resourceResolver == null || StringUtils.equals(ANONYMOUS, userId)) {
log.debug("Is Anonymous");
return ANONYMOUS;
} else {
log.debug("Is User: " + userId);
return userId;
}
}
}
// Should never happen, but when in doubt, treat as anonymous
log.debug("Failed through to Anonymous");
return ANONYMOUS;
}
@Override
public String getPath(SlingHttpServletRequest request) {
final String path = StringUtils.stripToNull(getParameterOrAttribute(request, PATH, null));
if (path == null) {
return path;
}
final ResourceResolver resourceResolver = request.getResourceResolver();
return resourceResolver.map(request, path);
}
@Override
public ResourceResolver getResourceResolverFor(final String authorizableId) throws LoginException, RepositoryException {
final Map<String, Object> authInfo = new HashMap<String, Object>();
Session adminSession = null;
try {
adminSession = slingRepository.loginAdministrative(null);
authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, adminSession);
final ResourceResolver resourceResolver = resourceResolverFactory.getResourceResolver(authInfo);
return resourceResolver;
} finally {
if (adminSession != null) {
adminSession.logout();
adminSession = null;
}
}
}
@Override
public void closeResourceResolverFor(ResourceResolver resourceResolver) {
try {
if (resourceResolver != null) {
resourceResolver.close();
}
} finally {
resourceResolver = null;
}
}
private boolean isAnonymous(SlingHttpServletRequest request) {
return StringUtils.equals(ANONYMOUS, getAuthorizableId(request));
}
private String getAddInitProperty(String manager, String key, String value) {
if (StringUtils.isBlank(manager)) {
throw new IllegalArgumentException("Client Context Data Manager cannot be blank.");
} else if (StringUtils.isBlank(key)) {
throw new IllegalArgumentException("Key cannot be blank.");
}
return ";CQ_Analytics." + manager + ".addInitProperty('" + key + "','" + value + "');";
}
private String wrapWithAnonymousScope(String script) {
return ";(function() { " + script + "})();";
}
private String wrapWithManagerCheck(String script, String manager) {
return "if (CQ_Analytics && CQ_Analytics." + manager + ") {" + script + "}";
}
public AuthorizableResolution getAuthorizableResolution(SlingHttpServletRequest request) {
final WCMMode wcmMode = WCMMode.fromRequest(request);
if (wcmMode != null && !WCMMode.DISABLED.equals(wcmMode)) {
// If in Author Mode
final String authorizableId = getParameterOrAttribute(request, AUTHORIZABLE_ID, null);
if (StringUtils.isNotBlank(authorizableId)) {
log.debug("Use Impersonation");
return AuthorizableResolution.IMPERSONATION;
}
}
log.debug("Use Authentication");
return AuthorizableResolution.AUTHENTICATION;
}
private static String getParameterOrAttribute(HttpServletRequest request, String key, String dfault) {
String value = null;
if (request == null) {
return value;
}
if (StringUtils.isNotBlank(request.getParameter(key))) {
value = request.getParameter(key);
} else if (StringUtils.isNotBlank((String) request.getAttribute(key))) {
value = (String) request.getAttribute(key);
}
if (StringUtils.isBlank(value)) {
value = dfault;
}
return value;
}}