
com.adobe.granite.security.user.util.AuthorizableJSONWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
/*************************************************************************
*
* 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 extends Authorizable> 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 extends Authorizable> 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 extends Authorizable> 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 extends Authorizable> filterAuthorizableIterator(Iterator extends Authorizable> authorizablesIterator) {
if (resultingAuthorizablePredicate != null) {
return IteratorUtils.filteredIterator(authorizablesIterator, resultingAuthorizablePredicate);
}
return authorizablesIterator;
}
private void writeTotal(String key, Iterator extends Authorizable> 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