All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.granite.security.user.util.AuthorizableJSONWriter Maven / Gradle / Ivy

There is a newer version: 2025.3.19823.20250304T101418Z-250200
Show newest version
/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2011 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 **************************************************************************/
package com.adobe.granite.security.user.util;

import java.security.Principal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jcr.AccessDeniedException;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.RangeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;

import com.adobe.granite.xss.ProtectionContext;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.PredicateUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.Group;
import org.apache.jackrabbit.api.security.user.Query;
import org.apache.jackrabbit.api.security.user.QueryBuilder;
import org.apache.jackrabbit.api.security.user.User;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.commons.iterator.RangeIteratorAdapter;
import org.apache.jackrabbit.util.Text;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.io.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.granite.security.user.UserProperties;
import com.adobe.granite.security.user.UserPropertiesManager;
import com.adobe.granite.security.user.UserPropertiesService;
import com.adobe.granite.xss.JSONUtil;
import com.adobe.granite.xss.XSSFilter;
import com.day.cq.replication.ReplicationQueue;
import com.day.cq.replication.ReplicationStatus;

/**
 * AuthorizableJSONWriter is a utility to write a JSON serialization of users
 * and groups.
 *
 * 

Default Serialization

* * The default serialization of a given authorizable includes the following * information: *
    *
  • {@link PropConstants#TYPE}: with value "group" or "user" depending on * the type of authorizable.
  • *
  • {@link PropConstants#AUTHORIZABLE_ID}: the authorizable ID.
  • *
  • {@link PropConstants#DISPLAY_NAME}: the default display name.
  • *
  • {@link PropConstants#HOME}: The path to the home directory.
  • *
  • {@link PropConstants#IS_IMPERSONATED}: this property is only included * if the serialized authorizable is the same as the one associated with the * editing session. The flag is 'true' if the session has been obtained * through impersonation.
  • *
* *

Output Properties

* * In order to include additional information into the serialization a set of * output properties can be specified. Currently the following additional * properties are supported: * *
    *
  • {@link PropConstants#PRINCIPAL}: The principal name.
  • *
  • {@link PropConstants#MEMBER_OF}: Includes the serialization of the * groups this authorizable is member of (as visible to the editing session).
  • *
  • memberOfTotal: The total number of groups this authorizable is member * of. Note, that -1 may be returned if a limit has been specified and the * total amount of members exceeds the specified limit
  • *
  • {@link PropConstants#DECLARED_MEMBER_OF}: Includes the serialization * of those groups this authorizable is declared member of (as visible to the * editing session).
  • *
  • declaredMemberOfTotal: The number of those groups. Note, that -1 may * be returned if a limit has been specified and the total amount of * declared membership entries exceeds the specified limit.
  • *
  • {@link PropConstants#IMAGE}: The image information.
  • *
  • {@link PropConstants#MODIFICATION}: Modification information.
  • *
  • {@link PropConstants#REPLICATION}: Replication data.
  • *
  • {@link PropConstants#WILDCARD}: Includes all of the above properties * plus the user and group specific properties listed below (except the * *Total properties).
  • *
  • Any other property stored with an authorizable. The property to be * included must be referred with a relative path. E.g. 'profile/givenName' * would be used to retrieve a property 'givenName' that was stored in the * profile subnode. The wildcard '*' is used to include all user specific * properties of a subnode e.g. 'profile/*'. Note however, that this will * not include protected JCR specific properties. * The serialization of the these properties is hierarchical exposing * intermediate nodes as JSON objects.
  • *
* * In addition the following user specific properties can be specified: * *
    *
  • {@link PropConstants#IMPERSONATORS}: The serialization of those * users that can impersonate the target user (as visible to the * editing session).
  • *
  • impersonatorsTotal: The total amount of impersonators. Note, that -1 * may be returned if a limit has been specified and the total amount of * impersonators exceeds the specified limit.
  • *
  • {@link PropConstants#DISABLED}: The disabled text in case this * user is disabled.
  • *
* * And there are a couple of group specific properties: * *
    *
  • {@link PropConstants#MEMBERS}: The serialization of the members * of this group (as visible to the editing session).
  • *
  • membersTotal: The total number of members. Note, that -1 may be * returned if a limit has been specified and the total number exceeds the limit.
  • *
  • {@link PropConstants#DECLARED_MEMBERS}: The serialization of the * declared members of this group (as visible to the editing session).
  • *
  • declaredMembersTotal: The total number of declared members. * Note, that -1 is returned if a limit has been specified and the total * number exceeds the limit.
  • *
* *

Limit the number of serialized authorizables

* * For those properties that include serialization of additional authorizables * it is possible to set a limit, which is the maximum amount of authorizables * that should be serialized with the specified output property. * This can be achieved by calling {@link #setLimit(String, long)} where the * 'key' is the name of the output property. * *

Compatibility Notes

* * This class replaces CQ {@code AuthorizableJSONWriter}.
* Please note the following differences in usage: * *
 * - outputprop-keys should be relative paths
 * - removed old backwards compatibility lookup from user-prop to profile
 * - jcr:created and jcr:createdBy must be listed explicitly
 * - rep:userId omitted as this properties no longer exists and is identical to "authorizableId"
 * - the "id" key has been renamed to "authorizableId"
 * - the "groupName" key has been omitted as it seems wrong to have "name"
 *   and "id" and in addition a "groupName" which essential was the "id".
 * - basic option removed as this is covered by outputprops being empty.
 * - the cq specific 'table' view has been removed
 * 
*/ public class AuthorizableJSONWriter implements PropConstants { private final static Logger log = LoggerFactory.getLogger(AuthorizableJSONWriter.class); /* Diff to CQ AuthorizableJSONWriter ---------------------------------------------------------------------------- - outputprop-keys should be relative paths - removed backwards compatibility lookup from user-prop to profile - jcr:created and jcr:createdBy must be listed explicitly - rep:userId omitted as this properties no longer exists and is identical to "authorizableId" - the "id" key has been renamed to "authorizableId" - the "groupName" key has been omitted as it seems wrong to have "name" and "id" and in addition a "groupName" which essential was the "id". - basic option removed as this is covered by outputprops being empty. - the cq specific 'table' view has been removed */ private final Session session; private final UserPropertiesManager userPropertiesMgr; private final ResourceResolver resourceResolver; private final Set outputProps; private final XSSFilter xss; private Map limits = new HashMap(); private List filterPredicates; private Predicate resultingAuthorizablePredicate; /** * @param userPropertiesMgr the manager to access the profile. * @param resourceResolver the resource resolver * @param session the session * @param outputProps the output properties * @param xss the XSS protection service */ public AuthorizableJSONWriter(UserPropertiesManager userPropertiesMgr, ResourceResolver resourceResolver, Session session, Set outputProps, XSSFilter xss) { this.session = session; this.userPropertiesMgr = userPropertiesMgr; this.resourceResolver = resourceResolver; this.outputProps = (outputProps == null) ? Collections.emptySet() : Collections.unmodifiableSet(outputProps); this.xss = xss; } /** * Sets the limit for the number of authorizables included in the object * with the specified key. * * @param key The key as present in the output props. * @param limit The maximal number of entries to be created for the * given key. */ public void setLimit(String key, long limit) { limits.put(key, limit); } /** * Creates a predicate list based of the given filters array containing only the non blank filters and merges all of them in a single resulting authorizable predicate. * The {@code resultingAuthorizablePredicate} will validate an authorizable if any of the predicates from the list are valid. * * @param filters an array of given filters to use for creating a predicate list and a resulting predicate from that list */ public void setFilterPredicates(final String[] filters) { if (filters != null && filters.length > 0) { filterPredicates = new ArrayList(); for (String filter : filters) { if (!StringUtils.isBlank(filter)) { filterPredicates.add(new AuthorizableFilterPredicate(filter)); } } resultingAuthorizablePredicate = PredicateUtils.anyPredicate(filterPredicates); } } /** * Get the limit for the specified key. * * @param key Key as present in the output props defining the set of * special properties to be included in the response. * @return The limit of entries for the specified key or -1 if no limit has * been set before. */ private long getLimit(String key) { if (limits.containsKey(key)) { return limits.get(key); } else { return -1; } } /** * Write the data of the specified {@code authorizable} to the given * {@code writer}. * * @param writer The JSONWriter to be used. * @param authorizable The target authorizable. * @throws JSONException If an exception occurs. */ public void write(JSONWriter writer, Authorizable authorizable) throws JSONException { try { String id = authorizable.getID(); String path = authorizable.getPath(); Node n = null; if (session.nodeExists(path)) { n = session.getNode(path); } OutputProperties properties = OutputProperties.create(outputProps); writer.object(); // mandatory properties that are always present. writer.key(TYPE).value((authorizable.isGroup() ? TYPE_GROUP : TYPE_USER)); JSONUtil.writeWithProtected(writer, AUTHORIZABLE_ID, id, xss); JSONUtil.writeWithProtected(writer, DISPLAY_NAME, getDisplayName(id), xss); writer.key(HOME).value(path); if (id.equals(session.getUserID())) { writer.key(IS_IMPERSONATED).value(isImpersonated()); } // all optional properties that must be requested either by being // listed in the outputProps or by passing "null" outputProps set. if (properties.wildcardOrDoInclude(PRINCIPAL)) { writer.key(PRINCIPAL).value(authorizable.getPrincipal().getName()); } String memberOfCnt = MEMBER_OF + CNT; boolean includeMoCnt = properties.doInclude(memberOfCnt); if (properties.wildcardOrDoInclude(MEMBER_OF)) { writeAuthorizables(MEMBER_OF, filterAuthorizableIterator(authorizable.memberOf()), getLimit(OFFSET), getLimit(MEMBER_OF), includeMoCnt, writer); } else if (includeMoCnt) { writeTotal(memberOfCnt, filterAuthorizableIterator(authorizable.memberOf()), writer); } String declaredMemberOfCnt = DECLARED_MEMBER_OF + CNT; boolean includeDmoCnt = properties.doInclude(declaredMemberOfCnt); if (properties.wildcardOrDoInclude(DECLARED_MEMBER_OF)) { writeAuthorizables(DECLARED_MEMBER_OF, filterAuthorizableIterator(authorizable.declaredMemberOf()), getLimit(OFFSET), getLimit(MEMBER_OF), includeDmoCnt, writer); } else if (includeDmoCnt) { writeTotal(declaredMemberOfCnt, filterAuthorizableIterator(authorizable.declaredMemberOf()), writer); } if (!authorizable.isGroup()) { // user specific properties User user = (User) authorizable; String impersonatorsCnt = IMPERSONATORS + CNT; boolean includeImpCnt = properties.doInclude(impersonatorsCnt); if (properties.wildcardOrDoInclude(IMPERSONATORS)) { writeAuthorizables(IMPERSONATORS, filterAuthorizableIterator(getImpersonators(user)), getLimit(OFFSET), getLimit(IMPERSONATORS), includeImpCnt, writer); } else if (includeImpCnt) { writeTotal(impersonatorsCnt, filterAuthorizableIterator(getImpersonators(user)), writer); } if ((properties.wildcardOrDoInclude(DISABLED)) && user.isDisabled()) { writer.key(DISABLED).value(user.getDisabledReason()); } } else { // group specific properties Group gr = (Group) authorizable; String membersCnt = MEMBERS + CNT; boolean includeMmbrsCnt = properties.doInclude(membersCnt); // determine if this is the "everyone" group boolean isEveryone = "everyone".equalsIgnoreCase(gr.getPrincipal().getName()); // optimize the queries for all members and all declared members if this is the everyone group if (properties.wildcardOrDoInclude(MEMBERS)) { if (isEveryone) { writeEveryoneAuthorizables(MEMBERS, getLimit(OFFSET), getLimit(MEMBERS), includeMmbrsCnt, writer); } else { writeAuthorizables(MEMBERS, filterAuthorizableIterator(gr.getMembers()), getLimit(OFFSET), getLimit(MEMBERS), includeMmbrsCnt, writer); } } else if (includeMmbrsCnt) { if (isEveryone) { writeEveryoneTotal(membersCnt, getLimit(MEMBERS), writer); } else { writeTotal(membersCnt, filterAuthorizableIterator(gr.getMembers()), 0, getLimit(MEMBERS), writer); } } String declaredMembersCnt = DECLARED_MEMBERS + CNT; boolean includeDmmbrsCnt = properties.doInclude(declaredMembersCnt); if (properties.wildcardOrDoInclude(DECLARED_MEMBERS)) { if (isEveryone) { writeEveryoneAuthorizables(DECLARED_MEMBERS, getLimit(OFFSET), getLimit(MEMBERS), includeDmmbrsCnt, writer); } else { writeAuthorizables(DECLARED_MEMBERS, filterAuthorizableIterator(gr.getDeclaredMembers()), getLimit(OFFSET), getLimit(MEMBERS), includeDmmbrsCnt, writer); } } else if (includeDmmbrsCnt) { if (isEveryone) { writeEveryoneTotal(declaredMembersCnt, getLimit(MEMBERS), writer); } else { writeTotal(declaredMembersCnt, filterAuthorizableIterator(gr.getDeclaredMembers()), 0, getLimit(MEMBERS), writer); } } } if (properties.wildcardOrDoInclude(IMAGE)) { writeImageInformation(id, writer); } if (properties.wildcardOrDoInclude(MODIFICATION)) { writeModificationDates(n, writer); } if (properties.wildcardOrDoInclude(REPLICATION)) { Resource resource = resourceResolver.getResource(path); ReplicationStatus replicationStatus = null; if (resource != null) { replicationStatus = resource.adaptTo(ReplicationStatus.class); } writeReplication(replicationStatus, writer); } if (!properties.isEmpty()) { // re-arrange the remaining properties in order to be able // to provide hierarchical view to properties. Map propMap = new HashMap(); for (String prop : properties) { int pos = prop.lastIndexOf('/'); String relPath; String name; Map target = propMap; if (pos == -1) { relPath = ""; name = prop; } else { relPath = prop.substring(0, pos); name = (prop.length() <= pos+1) ? "" : prop.substring(pos+1); // build hierarchy from path segments String[] keys = Text.explode(relPath, '/', false); for (String key : keys) { if (!target.containsKey(key)) { HashMap t = new HashMap(); target.put(key, t); target = t; } else { target = (Map) target.get(key); } } } // individual properties and wildcard are collected in a set if (!target.containsKey("*_" + relPath)) { target.put("*_" + relPath, new HashSet()); } // make sure the wildcard prop replaces all other entries Set props = (Set) target.get("*_"+relPath); if (WILDCARD.equals(name)) { props.clear(); props.add(WILDCARD); } else if (!props.contains(WILDCARD) && !"".equals(name)) { props.add(name); } } // process the properties writePropMap(writer, propMap, authorizable, n); } writer.endObject(); } catch (RepositoryException e) { throw new JSONException(e); } } //------------------------------------------------------------< private >--- /** * Write authorizables in the everyone group. Will do a direct query to fetch all users in the environment as opposed to using * {@link Group#getMembers()} or {@link Group#getDeclaredMembers()} * * The executed {@code Query} will apply the limits at the query level, without the need to first fetch all users first * * @param key * JSON key for the authorizable object in the response * @param offset * refers to the offset within the full result set at which the returned result set should start * @param limit * sets the maximum size of the result set * @param includeTotal * {@code Boolean} indicating if the total number of results should be included in the response * @param writer * {@code JSONWriter} object where the response will be written to * @throws JSONException * @throws RepositoryException */ private void writeEveryoneAuthorizables(String key, long offset, long limit, boolean includeTotal, JSONWriter writer) throws JSONException, RepositoryException { // do a query for everyone group instead of calling Group#getMembers() Iterator allMembers = new SkipIterator(Collections.emptySet().iterator()); if (offset < 0) { offset = 0; } try { UserManager userManager = resourceResolver.adaptTo(UserManager.class); // execute the query taking into account offset and limit allMembers = userManager.findAuthorizables(buildUserQuery(offset, limit)); } catch (Exception e) { log.error("Error while getting members for 'everyone' group!", e); } // write the obtained authorizables list writeAuthorizables(key, allMembers, 0, limit, includeTotal, writer); } private void writeAuthorizables(String key, Iterator authorizables, long offset, long limit, boolean includeTotal, JSONWriter writer) throws JSONException, RepositoryException { writer.key(key); writer.array(); int cnt = 0; long skipped = 0; boolean noLimit = (limit == -1); // create a SkipIterator from the authorizables Iterator so we can support pagination SkipIterator skipAuthorizablesIterator = SkipIterator.create(authorizables); // if offset > 0 need to skip that many items if (offset > 0) { skipped = skipAuthorizablesIterator.skip(offset); } // write authorizable info as long as limit is not reached. while (skipAuthorizablesIterator.hasNext() && (noLimit || cnt < limit)) { Authorizable auth = skipAuthorizablesIterator.next(); writer.object(); String id = auth.getID(); writer.key(AUTHORIZABLE_ID).value(id); JSONUtil.writeWithProtected(writer, DISPLAY_NAME, getDisplayName(id), xss); writer.key(HOME).value(auth.getPath()); writer.endObject(); cnt++; } writer.endArray(); // if total number must be included as well pass the current cnt // and let 'writeTotal' find out if cnt already reflects the total number. // Take the skipped number into account when computing total if (includeTotal) { writeTotal(key + CNT, skipAuthorizablesIterator, skipped + cnt, limit, writer); } } private void writeTotal(String key, Iterator authorizables, JSONWriter writer) throws JSONException { writeTotal(key, authorizables, 0, -1, writer); } /** * Write total for the everyone group. Will do a direct query to fetch all users in the environment as opposed to using * {@link Group#getMembers()} or {@link Group#getDeclaredMembers()} * * Initially the limits were applied after fetching all users with a severe performance impact. * The executed {@code Query} will apply the limits at the query level. * * @param key * JSON key for the authorizable object in the response * @param limit * The total count that will be written to the response will be capped at this value. * If total <= limit, the actual total will be present in the response * If total > limit, the response will contain -1 as the total * @param writer * {@code JSONWriter} object where the response will be written to * @throws JSONException */ private void writeEveryoneTotal(String key, final long limit, JSONWriter writer) throws JSONException { // do a query for everyone group instead of calling Group#getMembers() Iterator allMembers = new SkipIterator(Collections.emptySet().iterator()); try { UserManager userManager = resourceResolver.adaptTo(UserManager.class); allMembers = userManager.findAuthorizables(buildUserQuery(0, limit)); } catch (Exception e) { log.error("Error while getting members for 'everyone' group!", e); } writeTotal(key, allMembers, 0, limit, writer); } /** * Creates a new {@code Query} that will search through all users users, having the limits applied at the query level * * @param offset * refers to the offset within the full result set at which the returned result set should start * @param limit * sets the maximum size of the result set * @return a {@code Query} object */ private Query buildUserQuery(final long offset, final long limit) { return new Query() { public void build(QueryBuilder builder) { try { builder.setSelector(User.class); /** * If limit is set, request one extra item so we can detect more entries exist */ long maxResults = limit; if (maxResults > 0) { maxResults += 1; } builder.setLimit(offset, maxResults); if (filterPredicates != null && filterPredicates.size() > 0) { AuthorizableFilterPredicate filterPredicate = filterPredicates.get(0); // in case of multiple filters, all are merged in a single condition using the or operator which means that if any // of the merged condition is true, the resulting condition will be true if (filterPredicates.size() > 1) { T partialCondition = builder.or(builder.nameMatches(filterPredicate.getFilter()), builder.like(AuthorizableUtil.GIVEN_NAME, filterPredicate.getFilter())); for (int index = 1; index < filterPredicates.size(); index++) { filterPredicate = filterPredicates.get(index); partialCondition = builder.or( partialCondition, builder.or(builder.nameMatches(filterPredicate.getFilter()), builder.like(AuthorizableUtil.GIVEN_NAME, filterPredicate.getFilter()))); } builder.setCondition(partialCondition); } else { //the condition is that the authorizable ID or the authorizable given name must match the filter builder.setCondition(builder.or(builder.nameMatches(filterPredicate.getFilter()), builder.like(AuthorizableUtil.GIVEN_NAME, filterPredicate.getFilter()))); } } } catch (Exception e) { throw new IllegalArgumentException(e); } } }; } /** * Filters the given authorizable iterator by using the {@code resultingAuthorizablePredicate} predicate, if it is set. * * @param authorizablesIterator * the authorizables iterator to apply the filter on * @return the iterator filtered using the {@code resultingAuthorizablePredicate} predicate */ private Iterator filterAuthorizableIterator(Iterator authorizablesIterator) { if (resultingAuthorizablePredicate != null) { return IteratorUtils.filteredIterator(authorizablesIterator, resultingAuthorizablePredicate); } return authorizablesIterator; } private void writeTotal(String key, Iterator authorizables, long startCnt, long limit, JSONWriter writer) throws JSONException { long cnt = startCnt; if (authorizables instanceof RangeIterator) { // the size can be retrieved without iterating. cnt = ((RangeIterator) authorizables).getSize(); } else { boolean noLimit = (limit == -1); while (authorizables.hasNext() && (noLimit || cnt < limit)) { authorizables.next(); cnt++; } // test if the specified limit prevented the calculation of the total // size in which case -1 is returned to indicate that there are // more entries than the specified limit. if (authorizables.hasNext()) { cnt = -1; } } writer.key(key).value(cnt); } private void writeModificationDates(Node n, JSONWriter writer) throws JSONException { writer.key(MODIFICATION).object(); if (n != null) { try { if (n.hasProperty(Property.JCR_LAST_MODIFIED)) { Calendar lastMod = n.getProperty(Property.JCR_LAST_MODIFIED).getDate(); writer.key("lastModified").value(lastMod.getTimeInMillis()); } if (n.hasProperty(Property.JCR_LAST_MODIFIED_BY)) { String lastModBy = n.getProperty(Property.JCR_LAST_MODIFIED_BY).getString(); writer.key("lastModifiedBy").value(getDisplayName(lastModBy)); } } catch (RepositoryException e) { log.error("Failed to retrieve modification information.", e.getMessage()); } } writer.endObject(); } private void writeReplication(ReplicationStatus replicationStatus, JSONWriter writer) throws JSONException { writer.key(REPLICATION).object(); if (replicationStatus != null) { int maxQueuePos = -1; for (ReplicationQueue.Entry e : replicationStatus.getPending()) { if (e.getQueuePosition() > maxQueuePos) { maxQueuePos = e.getQueuePosition(); } } writer.key("numQueued").value(maxQueuePos+1); Calendar last = replicationStatus.getLastPublished(); if (last != null) { writer.key("published").value(last.getTimeInMillis()); String publishedBy = replicationStatus.getLastPublishedBy(); try { String authorizableId = replicationStatus.getLastPublishedBy(); UserProperties up = userPropertiesMgr.getUserProperties(authorizableId, UserPropertiesService.PROFILE_PATH); if (up != null) { publishedBy = up.getDisplayName(); } } catch (RepositoryException e) { log.warn("Failed to retrieve display name for {0}: Using publishedBy value instead.", publishedBy); } JSONUtil.writeWithProtected(writer, "publishedBy", publishedBy, xss); if (replicationStatus.getLastReplicationAction() != null) { writer.key("action").value(replicationStatus.getLastReplicationAction().name()); } } } writer.endObject(); } private void writeImageInformation(String id, JSONWriter writer) throws JSONException { UserProperties profile = getProfile(id); if (profile != null) { try { Resource photo = profile.getResource(UserProperties.PHOTOS); if (photo != null) { Resource image = photo.getResourceResolver().getResource(photo, "primary/image"); if (image != null) { Node photoNode = photo.adaptTo(Node.class); String ext = "png"; long ck = 0; String lmPath = "image/" + JcrConstants.JCR_LASTMODIFIED; if (photoNode.hasProperty(lmPath)) { // build cache killer from modification date of thumbnail ck = photoNode.getProperty(lmPath).getLong(); // remove milliseconds in order to match the mod date provided by json servlets ck = ck / 1000 * 1000; } writer.key("picturePath").value(photo.getPath()); writer.key("pictureExt").value(ext); writer.key("pictureMod").value(ck); } } else { writer.key("thumbnail").value(""); } } catch (RepositoryException e) { throw new JSONException(e); } } } private UserProperties getProfile(String id) { if (userPropertiesMgr != null) { try { return userPropertiesMgr.getUserProperties(id, UserPropertiesService.PROFILE_PATH); } catch (RepositoryException e) { log.debug("Could not access Repository, when trying to access full name of {}: {}", id, e); } } return null; } private String getDisplayName(String id) { String name = id; UserProperties up = getProfile(id); if (up != null) { try { name = up.getDisplayName(); } catch (RepositoryException e) { log.debug("Cannot retrieve display name for " + id); } } return name; } private Iterator getImpersonators(User user) { Collection res = new HashSet(); if (session instanceof JackrabbitSession) { try { PrincipalIterator itr = user.getImpersonation().getImpersonators(); while (itr.hasNext()) { Principal pr = itr.nextPrincipal(); Authorizable authorizable = ((JackrabbitSession) session).getUserManager().getAuthorizable(pr); if (authorizable != null) { res.add(authorizable); } else { log.debug("Could not resolve impersonator principal {} -> ignoring", pr.getName()); } } } catch (AccessDeniedException e) { log.debug("Cannot retrieve impersonators : Access Denied."); } catch (RepositoryException e) { log.warn("Cannot retrieve impersonators", e.getMessage()); } } return new RangeIteratorAdapter(res.iterator(), res.size()); } /** * Returns {@code true} if the reading session has been obtained by * impersonation. This is the case the session has the corresponding attribute * set. */ private boolean isImpersonated() { return session != null && session.getAttribute(ResourceResolver.USER_IMPERSONATOR) != null; } /** * Write the propMap containing hierarchical property information to the * given JSON writer. * * @param writer The JSON writer. * @param propMap A map whose values are either Set of String (to describe * the set of properties to be written or Map (indicating the next child level). * @param authorizable The target authorizable. * @param n The associated node. * @throws JSONException * @throws RepositoryException */ private void writePropMap(JSONWriter writer, Map propMap, Authorizable authorizable, Node n) throws JSONException, RepositoryException { for (String key : propMap.keySet()) { if (key.startsWith("*_")) { String relPath = key.substring(2); writeProperties(writer, authorizable, n, relPath, (Set) propMap.get(key)); } else { Object v = propMap.get(key); //encode the property key before returning it to the user - avoiding XSS exploit writer.key(xss.filter(ProtectionContext.PLAIN_HTML_CONTENT, key)); writer.object(); writePropMap(writer, (Map) v, authorizable, n); writer.endObject(); } } } /** * Write the properties specified by the given name-set to the specified * JSON writer. The parameter {@code relPath} indicates the relative * position of the requested property names regarding the specified * authorizable. * * @param writer The JSON writer. * @param authorizable The target authorizable. * @param n The associated node. * @param relPath The relative path. * @param names The name of the properties to be written. * @throws JSONException * @throws RepositoryException */ private void writeProperties(JSONWriter writer, Authorizable authorizable, Node n, String relPath, Set names) throws JSONException, RepositoryException { if (names.contains(WILDCARD)) { UserProperties up = userPropertiesMgr.getUserProperties(authorizable.getID(), relPath); if (up != null) { String[] propNames = up.getPropertyNames(); for (String propName : propNames) { String[] values = up.getProperty(propName, null, String[].class); writeValues(writer, propName, values); } } } else { for (String name : names) { String propPath = ("".equals(relPath))? name : relPath + "/" + name; if (authorizable.hasProperty(propPath)) { Value[] vs = authorizable.getProperty(propPath); writeValues(writer, name, vs); } else if (n != null && n.hasProperty(propPath)) { // not an authorizable property -> try to retrieve it // form the node itself as they may not be exposed // on the authorizable (e.g. jcr:* properties) Property p = n.getProperty(propPath); if (p.isMultiple()) { writeValues(writer, name, p.getValues()); } else { JSONUtil.writeWithProtected(writer, name, p.getString(), xss); } } } } } /** * Write an array of String to the writer. * * @param writer * @param key * @param values */ private void writeValues(JSONWriter writer, String key, String[] values) throws JSONException { if (values == null) { return; } switch (values.length) { case 0: JSONUtil.writeWithProtected(writer, key, "", xss); break; case 1: JSONUtil.writeWithProtected(writer, key, values[0], xss); break; default: JSONUtil.writeWithProtected(writer, key, values, xss); } } /** * Write an array of jcr values to the writer. * * @param writer * @param key * @param values */ private void writeValues(JSONWriter writer, String key, Value[] values) throws JSONException, RepositoryException { if (values == null) { return; } switch (values.length) { case 0: JSONUtil.writeWithProtected(writer, key, "", xss); break; case 1: JSONUtil.writeWithProtected(writer, key, values[0].getString(), xss); break; default: String[] strs = new String[values.length]; for (int i = 0; i < values.length; i++) { strs[i] = values[i].getString(); } JSONUtil.writeWithProtected(writer, key, strs, xss); } } //-------------------------------------------------------------------------- private static final class OutputProperties extends HashSet { private final boolean wildcard; private OutputProperties(boolean wildcard, int size) { super(size); this.wildcard = wildcard; } private static OutputProperties create(Set base) { if (base == null) { return new OutputProperties(false, 0); } else { OutputProperties props = new OutputProperties(base.contains(WILDCARD), base.size()); for (String propName : base) { if (!WILDCARD.equals(propName)) { props.add(propName); } } return props; } } private boolean doInclude(String propName) { return remove(propName); } private boolean wildcardOrDoInclude(String propName) { return wildcard || doInclude(propName); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy