LdapFilter.java
/*
* GovWay - A customizable API Gateway
* https://govway.org
*
* Copyright (c) 2005-2024 Link.it srl (https://link.it).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3, as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openspcoop2.utils.transport.ldap;
import java.text.ParseException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
/**
* classe che implementa i filtri di ricerca
*
* @author Tommaso Burlon (tommaso.burlon@link.it)
* @version $Rev$, $Date$
*
*/
public class LdapFilter {
private String key = null;
private String value = null;
private List<LdapFilter> subFilters = null;
private FilterType type = null;
private enum FilterType {
AND("&"), OR("|"), NOT("!"), EQUALS("="), PRESENCE("=*"), GTE(">="), LTE("<="), TRUE(null), FALSE(null);
private String operator;
private FilterType(String op) {
this.operator = op;
}
@Override
public String toString() {
return this.operator;
}
public static FilterType fromString(String str) {
FilterType[] types = FilterType.values();
for (FilterType type : types)
if (type.toString().equals(str))
return type;
return null;
}
}
private LdapFilter() {}
private LdapFilter(FilterType type) {
this.type = type;
}
private LdapFilter(FilterType type, String key, String value) {
this.key = key;
this.value = value;
this.type = type;
}
private LdapFilter(FilterType type, List<LdapFilter> subFilters) {
this.subFilters = subFilters;
this.type = type;
}
private String getOperator() {
return this.type.toString();
}
private StringBuilder buildString(StringBuilder ret) {
switch (this.type) {
case AND:
case OR:
case NOT:
ret.append("(").append(this.getOperator());
for (LdapFilter subFilter : this.subFilters) {
subFilter.buildString(ret);
}
ret.append(")");
break;
case EQUALS:
case PRESENCE:
case GTE:
case LTE:
ret.append("(").append(this.key).append(this.getOperator()).append(this.value).append(")");
break;
case TRUE:
ret.append("(&)");
break;
case FALSE:
ret.append("(|)");
break;
}
return ret;
}
@Override
public String toString() {
StringBuilder ret = new StringBuilder();
return this.buildString(ret).toString();
}
// operatori
public static LdapFilter and(LdapFilter ...filters) {
return new LdapFilter(FilterType.AND, List.of(filters));
}
public boolean isAndFilter() {
return this.type.equals(FilterType.AND);
}
public static LdapFilter or(LdapFilter ...filters) {
return new LdapFilter(FilterType.OR, List.of(filters));
}
public boolean isOrFilter() {
return this.type.equals(FilterType.OR);
}
public static LdapFilter not(LdapFilter filter) {
return new LdapFilter(FilterType.NOT, List.of(filter));
}
public boolean isNotFilter() {
return this.type.equals(FilterType.NOT);
}
public static LdapFilter absoluteTrue() {
return new LdapFilter(FilterType.TRUE);
}
public boolean isAbsoluteTrueFilter() {
return this.type.equals(FilterType.TRUE);
}
public static LdapFilter absoluteFalse() {
return new LdapFilter(FilterType.FALSE);
}
public boolean isAbsoluteFalseFilter() {
return this.type.equals(FilterType.FALSE);
}
// condizioni di base
public static LdapFilter isPresent(String attribute) {
return new LdapFilter(FilterType.PRESENCE, attribute, "");
}
public static LdapFilter isEqual(String attribute, String value) {
return new LdapFilter(FilterType.EQUALS, attribute, value);
}
public static LdapFilter isAbsent(String attribute) {
return LdapFilter.not(LdapFilter.isPresent(attribute));
}
public static LdapFilter isGreaterEqual(String attribute, String value) {
return new LdapFilter(FilterType.GTE, attribute, value);
}
public static LdapFilter isLessEqual(String attribute, String value) {
return new LdapFilter(FilterType.LTE, attribute, value);
}
// parse del filtro da una stringa
private static final Pattern operatorsPatern = Pattern.compile("(=|<=|>=|\\&|\\|)");
private static LdapFilter parseCondition(String raw, int start, int end) throws ParseException {
if (raw.charAt(start) != '(')
throw new ParseException("la condizione non inizia con una parentesi", start);
if (raw.charAt(end - 1) != ')')
throw new ParseException("la condizione non finisce con una parentesi", end - 1);
Matcher matcher = operatorsPatern.matcher(raw).region(start, end);
if (!matcher.find())
throw new ParseException("operatore non presente o non riconusciuto operatori possibili: =,<=,>=,&,|", start + 1);
if (matcher.groupCount() != 1)
throw new ParseException("inseriti piu operatori in una signola condizione", matcher.end());
String key = raw.substring(start + 1, matcher.start(0));
FilterType op = FilterType.fromString(raw.substring(matcher.start(0), matcher.end(0)));
String value = raw.substring(matcher.end(0), end - 1);
if (op == null)
throw new ParseException("operatore non riconusciuto operatori possibili: =,<=,>=,&,|,=*", start + 1);
if (op.equals(FilterType.OR) || op.equals(FilterType.AND)) {
if (key.isEmpty() && value.isEmpty())
return op.equals(FilterType.OR) ? LdapFilter.absoluteFalse() : LdapFilter.absoluteTrue();
throw new ParseException("condizione non valida, atteso absolute true/false ma chiave valore presenti", start + 1);
}
if (op.equals(FilterType.EQUALS) && value.equals("*"))
return LdapFilter.isPresent(key);
return new LdapFilter(op, key, value);
}
private static LdapFilter parseOperator(String raw, int start, int end, List<LdapFilter> subFilters) throws ParseException {
if (raw.length() <= start + 1 || raw.length() < end)
throw new ParseException("lunghezza stringa operatore tropo breve", start + 1);
char op = raw.charAt(start + 1);
FilterType type = null;
if (op == '|') type = FilterType.OR;
if (op == '&') type = FilterType.AND;
if (op == '!') type = FilterType.NOT;
if (type == null) throw new ParseException("tipo operatore di aggregazione ldap non riconosciuto: " + op, start + 1);
return new LdapFilter(type, subFilters);
}
public static LdapFilter parse(String raw) throws ParseException {
boolean isCondition = true;
String in = raw.replace(" ", "");
Deque<List<LdapFilter>> filters = new ArrayDeque<>();
Deque<Integer> brackets = new ArrayDeque<>();
filters.push(new ArrayList<>());
for (int i = 0; i < in.length(); i++) {
if(in.charAt(i) == '(') {
brackets.push(i);
filters.push(new ArrayList<>());
isCondition = true;
}
if (in.charAt(i) == ')') {
if (brackets.isEmpty())
throw new ParseException("parentesi chiusa non accoppiata con una aperta", i);
int prev = brackets.pop();
if (isCondition) {
filters.pop();
filters.peek().add(parseCondition(in, prev, i + 1));
isCondition = false;
} else {
List<LdapFilter> subFilters = filters.pop();
filters.peek().add(parseOperator(in, prev, i + 1, subFilters));
}
}
}
return filters.peek().get(0);
}
// controlla se un attributo rispetta il filtro
public boolean check(Attributes attrs) {
try {
switch (this.type) {
case TRUE: return false;
case FALSE: return true;
case AND:
for (LdapFilter filter : this.subFilters)
if (!filter.check(attrs))
return false;
return true;
case OR:
for (LdapFilter filter : this.subFilters)
if (filter.check(attrs))
return true;
return false;
case NOT:
return !this.subFilters.get(0).check(attrs);
case EQUALS:
if (attrs.get(this.key) == null) return false;
Pattern pattern = Pattern.compile(this.value.replace("*", ".*"), Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher((String)attrs.get(this.key).get(0));
return matcher.find();
case PRESENCE:
return attrs.get(this.key) != null;
case GTE: return this.value.compareTo((String)attrs.get(this.key).get(0)) <= 0;
case LTE: return this.value.compareTo((String)attrs.get(this.key).get(0)) >= 0;
default: return false;
}
} catch(NamingException e) {
return false;
}
}
}