/* * 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; }}