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

com.day.cq.security.util.AuthorizableQueryManager Maven / Gradle / Ivy

There is a newer version: 6.5.21
Show newest version
/*
 * Copyright 1997-2010 Day Management AG
 * Barfuesserplatz 6, 4001 Basel, Switzerland
 * All Rights Reserved.
 *
 * This software is the confidential and proprietary information of
 * Day Management AG, ("Confidential Information"). You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Day.
 */

package com.day.cq.security.util;

import com.adobe.granite.security.user.UserProperties;
import com.day.cq.commons.RangeIterator;
import org.apache.jackrabbit.api.JackrabbitSession;
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.NodeIteratorAdapter;
import org.apache.jackrabbit.commons.json.JsonHandler;
import org.apache.jackrabbit.commons.json.JsonParser;
import org.apache.sling.api.SlingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.query.QueryManager;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Stack;

/**
 * This class handles the translation of queries for users and groups from a JSON format to
 * the query model of Jackrabbit's user groups search
 * (see {@link org.apache.jackrabbit.api.security.user.UserManager#findAuthorizables(org.apache.jackrabbit.api.security.user.Query) UserManager#findAuthorizables(Query)}).
 *
 * The JSON query format is defined as follows:
 * 
 {
 ( selector: "authorizable" | "user" | "group" )?        // Defaults to "authorizable", see QueryBuilder#setSelector()

 (
 scope:                                                // See QueryBuilder#setScope()
 {
 groupName: /* group name (String) * /
 ( declaredOnly: true | false )                      // Defaults to true
 }
 ) ?                                                     // Defaults to all

 ( condition: [ CONJUNCTION+ ] ) ?                       // Defaults to a 'true' condition, see QueryBuilder#setCondition()

 (
 order | sort:                                         // See QueryBuilder#setOrder()
 {
 property: /* relative path (String) * /
 ( direction: "asc" | "desc" )                       // Defaults to "asc"
 }
 ) ?                                                     // Defaults to document order

 (
 limit:                                                // See QueryBuilder#setLimit()
 {
 offset: /* Positive Integer * /                     // Takes precedence over bound if both are given
 bound:  /* String, Number, Boolean * /
 max:    /* Positive Integer or -1 * /               // Defaults to no limit (-1)
 }
 ) ?                                                     // Defaults to all
 }

 CONJUNCTION ::= COMPOUND | PRIMITIVE
 COMPOUND    ::= [ PRIMITIVE+ ]
 PRIMITIVE   ::= { ATOM | NEGATION }
 NEGATION    ::= not: { ATOM }                             // See QueryBuilder#not()
 ATOM        ::= named: /* pattern * /                     // Users, groups of that name. See QueryBuilder#nameMatches()
 |   exists: /* relative path * /              // See QueryBuilder#exists()
 |   impersonates: /* authorizable name * /    // See QueryBuilder#impersonates()
 |   RELOP:
 {
 property: /* relative path * /
 value: /* String, Number, Boolean * /   // According to the type of the property
 }
 |   like:                                     // See QueryBuilder#like()
 {
 property: /* relative path * /
 pattern: /* pattern * /
 }
 |   contains:                                 // See QueryBuilder#contains()
 {
 property: /* relative path * /
 expression: /* search expression * /
 }
 RELOP       ::= neq | eq | lt | le | gt | ge              // See QueryBuilder#neq(), QueryBuilder#eq(), ...
 
* *
    *
  • A relative path refers to a property or a child node of an user or a group. Property names need to be * prefixed with the at (@) character. Invalid JCR characters need proper escaping. The current path is denoted * by a dot (.).
  • *
  • In a 'pattern' the percent character (%) represents any string of zero or more characters and the underscore * character (_) represents any single character. Any literal use of these characters and the backslash * character (\) must be escaped with a backslash character. The pattern is matched against * Authorizable#getID() and Authorizable#getPrincipal().
  • *
  • The syntax of 'expression' is [-]value { [OR] [-]value }.
  • *
* * @deprecated cq 5.5 Use {@link org.apache.jackrabbit.commons.jackrabbit.user.AuthorizableQueryManager} instead. */ public class AuthorizableQueryManager { public static final int MAX_RESULT_COUNT = 2000; private static final Logger log = LoggerFactory.getLogger(AuthorizableQueryManager.class); private final UserManager userManager; private final ValueFactory valueFactory; public AuthorizableQueryManager(UserManager userManager, ValueFactory valueFactory) { this.userManager = userManager; this.valueFactory = valueFactory; } public Iterator execute(final String query) throws RepositoryException, IOException { try { return userManager.findAuthorizables(new Query() { public void build(QueryBuilder builder) { try { // Must request more than MAX_RESULT_COUNT records explicitly builder.setLimit(0, MAX_RESULT_COUNT); new QueryTranslator(builder).translate(query); } catch (IOException e) { throw new IllegalArgumentException(e); } } }); } catch (IllegalArgumentException e) { Throwable cause = e.getCause(); if (cause instanceof IOException) { throw (IOException) cause; } else { throw e; } } } //------------------------------------------< private >--- private class QueryTranslator implements JsonHandler { private final QueryBuilder queryBuilder; private final Stack handlers = new Stack(); public QueryTranslator(QueryBuilder queryBuilder) { this.queryBuilder = queryBuilder; handlers.push(new HandlerBase() { @Override public void object() { handlers.push(new ClausesHandler()); } }); } public void translate(String query) throws IOException { new JsonParser(this).parse(query); if (handlers.size() != 1) { throw new IOException("Missing closing parenthesis"); } } public void object() throws IOException { handlers.peek().object(); } public void endObject() throws IOException { handlers.peek().endObject(); } public void array() throws IOException { handlers.peek().array(); } public void endArray() throws IOException { handlers.peek().endArray(); } public void key(String s) throws IOException { handlers.peek().key(s); } public void value(String s) throws IOException { handlers.peek().value(s); } public void value(boolean b) throws IOException { handlers.peek().value(b); } public void value(long l) throws IOException { handlers.peek().value(l); } public void value(double v) throws IOException { handlers.peek().value(v); } private Value valueFor(String s) { return valueFactory.createValue(s); } private Value valueFor(boolean b) { return valueFactory.createValue(b); } private Value valueFor(long l) { return valueFactory.createValue(l); } private Value valueFor(double v) { return valueFactory.createValue(v); } //------------------------------------------< HandlerBase >--- private class HandlerBase implements JsonHandler { public void object() throws IOException { throw new IOException("Syntax error: '{'"); } public void endObject() throws IOException { throw new IOException("Syntax error: '}'"); } public void array() throws IOException { throw new IOException("Syntax error: '['"); } public void endArray() throws IOException { throw new IOException("Syntax error: ']'"); } public void key(String s) throws IOException { throw new IOException("Syntax error: key '" + s + '\''); } public void value(String s) throws IOException { throw new IOException("Syntax error: string '" + s + '\''); } public void value(boolean b) throws IOException { throw new IOException("Syntax error: boolean '" + b + '\''); } public void value(long l) throws IOException { throw new IOException("Syntax error: long '" + l + '\''); } public void value(double v) throws IOException { throw new IOException("Syntax error: double '" + v + '\''); } } //------------------------------------------< ClausesHandler >--- private class ClausesHandler extends HandlerBase { private String currentKey; @Override public void object() throws IOException { handlers.push(handlerFor(currentKey)); } @Override public void endObject() throws IOException { handlers.pop(); } @Override public void array() throws IOException { handlers.push(handlerFor(currentKey)); } @Override public void endArray() throws IOException { handlers.pop(); } @Override public void key(String s) throws IOException { currentKey = s; } @Override public void value(String s) throws IOException { if ("selector".equals(currentKey)) { queryBuilder.setSelector(selectorFor(s)); } else { throw new IOException("String value '" + s + "' is invalid for '" + currentKey + '\''); } } private Class selectorFor(String selector) throws IOException { if ("user".equals(selector)) { return User.class; } else if ("group".equals(selector)) { return Group.class; } else if ("authorizable".equals(selector)) { return Authorizable.class; } else { throw new IOException("Invalid selector '" + selector + '\''); } } private JsonHandler handlerFor(String key) throws IOException { if ("scope".equals(key)) { return new ScopeHandler(); } else if ("condition".equals(key)) { return new ConditionHandler(); } else if ("order".equals(key) || "sort".equals(key)) { return new OrderHandler(); } else if ("limit".equals(key)) { return new LimitHandler(); } else { throw new IOException("Invalid clause '" + key + '\''); } } } //------------------------------------------< ScopeHandler >--- private class ScopeHandler extends HandlerBase { private String currentKey; private String groupName; private Boolean declaredOnly; @Override public void endObject() throws IOException { if (groupName == null) { throw new IOException("Missing groupName"); } else { queryBuilder.setScope(groupName, declaredOnly == null ? true : declaredOnly); } handlers.pop(); } @Override public void key(String s) throws IOException { currentKey = s; } @Override public void value(String s) throws IOException { if ("groupName".equals(currentKey)) { groupName = s; } else { throw new IOException("Unexpected: '" + currentKey + ':' + s + '\''); } } @Override public void value(boolean b) throws IOException { if ("declaredOnly".equals(currentKey)) { declaredOnly = b; } else { throw new IOException("Unexpected: '" + currentKey + ':' + b + '\''); } } } //------------------------------------------< ConditionHandler >--- private class ConditionHandler extends HandlerBase { private final List memberHandlers = new ArrayList(); @Override public void object() throws IOException { PrimitiveHandler memberHandler = new PrimitiveHandler(); memberHandlers.add(memberHandler); handlers.push(memberHandler); } @Override public void array() throws IOException { CompoundHandler memberHandler = new CompoundHandler(); memberHandlers.add(memberHandler); handlers.push(memberHandler); } @Override public void endArray() throws IOException { if (memberHandlers.isEmpty()) { throw new IOException("Empty search term"); } Iterator memberHandler = memberHandlers.iterator(); T condition = memberHandler.next().getCondition(); while (memberHandler.hasNext()) { condition = queryBuilder.and(condition, memberHandler.next().getCondition()); } queryBuilder.setCondition(condition); handlers.pop(); } } //------------------------------------------< ConditionBase >--- private abstract class ConditionBase extends HandlerBase { public abstract T getCondition(); } //------------------------------------------< CompoundHandler >--- private class CompoundHandler extends ConditionBase { private final List memberHandlers = new ArrayList(); @Override public void object() throws IOException { PrimitiveHandler memberHandler = new PrimitiveHandler(); memberHandlers.add(memberHandler); handlers.push(memberHandler); } @Override public void endArray() throws IOException { if (memberHandlers.isEmpty()) { throw new IOException("Empty search term"); } handlers.pop(); } @Override public T getCondition() { Iterator memberHandler = memberHandlers.iterator(); T condition = memberHandler.next().getCondition(); while (memberHandler.hasNext()) { condition = queryBuilder.or(condition, memberHandler.next().getCondition()); } return condition; } } //------------------------------------------< PrimitiveHandler >--- private class PrimitiveHandler extends ConditionBase { private String currentKey; private ConditionBase relOp; private ConditionBase not; private T condition; @Override public void object() throws IOException { if (hasCondition()) { throw new IOException("Condition on '" + currentKey + "' not allowed since another " + "condition is already set"); } if ("not".equals(currentKey)) { not = new PrimitiveHandler(); handlers.push(not); } else { relOp = new RelOpHandler(currentKey); handlers.push(relOp); } } @Override public void endObject() throws IOException { if (!hasCondition()) { throw new IOException("Missing term"); } if (relOp != null) { condition = relOp.getCondition(); } else if (condition == null) { condition = queryBuilder.not(not.getCondition()); } handlers.pop(); } @Override public void key(String s) throws IOException { currentKey = s; } @Override public void value(String s) throws IOException { if (hasCondition()) { throw new IOException("Condition on '" + currentKey + "' not allowed since another " + "condition is already set"); } if ("named".equals(currentKey)) { condition = queryBuilder.nameMatches(s); } else if ("exists".equals(currentKey)) { condition = queryBuilder.exists(s); } else if ("impersonates".equals(currentKey)) { condition = queryBuilder.impersonates(s); } else { throw new IOException("Invalid condition '" + currentKey + '\''); } } private boolean hasCondition() { return condition != null || relOp != null || not != null; } @Override public T getCondition() { return condition; } } //------------------------------------------< RelOpHandler >--- private class RelOpHandler extends ConditionBase { private final String op; private String currentKey; private String property; private String pattern; private String expression; private Value value; private T condition; public RelOpHandler(String op) { this.op = op; } @Override public void endObject() throws IOException { if (property == null) { throw new IOException("Property not set for condition '" + op + '\''); } if ("like".equals(op)) { if (pattern == null) { throw new IOException("Pattern not set for 'like' condition"); } condition = queryBuilder.like(property, pattern); } else if ("contains".equals(op)) { if (expression == null) { throw new IOException("Expression not set for 'contains' condition"); } condition = queryBuilder.contains(property, expression); } else { if (value == null) { throw new IOException("Value not set for '" + op + "' condition"); } if ("eq".equals(op)) { condition = queryBuilder.eq(property, value); } else if ("neq".equals(op)) { condition = queryBuilder.neq(property, value); } else if ("lt".equals(op)) { condition = queryBuilder.lt(property, value); } else if ("le".equals(op)) { condition = queryBuilder.le(property, value); } else if ("ge".equals(op)) { condition = queryBuilder.ge(property, value); } else if ("gt".equals(op)) { condition = queryBuilder.gt(property, value); } else { throw new IOException("Invalid condition: '" + op + '\''); } } handlers.pop(); } @Override public void key(String s) throws IOException { currentKey = s; } @Override public void value(String s) throws IOException { if ("property".equals(currentKey)) { property = s; } else if ("pattern".equals(currentKey)) { pattern = s; } else if ("expression".equals(currentKey)) { expression = s; } else if ("value".equals(currentKey)) { value = valueFor(s); } else { throw new IOException("Expected one of 'property', 'pattern', 'expression', 'value' " + "but found '" + currentKey + '\''); } } @Override public void value(boolean b) throws IOException { if ("value".equals(currentKey)) { value = valueFor(b); } else { throw new IOException("Expected 'value', found '" + currentKey + '\''); } } @Override public void value(long l) throws IOException { if ("value".equals(currentKey)) { value = valueFor(l); } else { throw new IOException("Expected 'value', found '" + currentKey + '\''); } } @Override public void value(double v) throws IOException { if ("value".equals(currentKey)) { value = valueFor(v); } else { throw new IOException("Expected 'value', found '" + currentKey + '\''); } } @Override public T getCondition() { return condition; } } //------------------------------------------< OrderHandler >--- private class OrderHandler extends HandlerBase { private String currentKey; private String property; private QueryBuilder.Direction direction; @Override public void endObject() throws IOException { if (property == null) { throw new IOException("Missing property"); } else { queryBuilder.setSortOrder(property, direction == null ? QueryBuilder.Direction.ASCENDING : direction, true); } handlers.pop(); } @Override public void key(String s) throws IOException { currentKey = s; } @Override public void value(String s) throws IOException { if ("property".equals(currentKey)) { property = s; } else if ("direction".equals(currentKey)) { direction = directionFor(s); } else { throw new IOException("Unexpected: '" + currentKey + ':' + s + '\''); } } private QueryBuilder.Direction directionFor(String direction) throws IOException { if ("asc".equals(direction)) { return QueryBuilder.Direction.ASCENDING; } else if ("desc".equals(direction)) { return QueryBuilder.Direction.DESCENDING; } else { throw new IOException("Invalid direction '" + direction + '\''); } } } //------------------------------------------< LimitHandler >--- private class LimitHandler extends HandlerBase { private String currentKey; private Long offset; private Value bound; private Long max; @Override public void endObject() throws IOException { if (offset != null) { queryBuilder.setLimit(offset, max == null ? -1 : max); } else if (bound != null) { queryBuilder.setLimit(bound, max == null ? -1 : max); } else { throw new IOException("Missing bound or offset"); } handlers.pop(); } @Override public void key(String s) throws IOException { currentKey = s; } @Override public void value(String s) throws IOException { if ("bound".equals(currentKey)) { bound = valueFor(s); } else { throw new IOException("Unexpected: '" + currentKey + ':' + s + '\''); } } @Override public void value(boolean b) throws IOException { if ("bound".equals(currentKey)) { bound = valueFor(b); } else { throw new IOException("Unexpected: '" + currentKey + ':' + b + '\''); } } @Override public void value(long l) throws IOException { if ("bound".equals(currentKey)) { bound = valueFor(l); } else if ("offset".equals(currentKey)) { offset = l; } else if ("max".equals(currentKey)) { max = l; } else { throw new IOException("Unexpected: '" + currentKey + ':' + l + '\''); } } @Override public void value(double v) throws IOException { if ("bound".equals(currentKey)) { bound = valueFor(v); } else { throw new IOException("Unexpected: '" + currentKey + ':' + v + '\''); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy