/** * Copyright (C) 2010-2017 Structr GmbH * * This file is part of Structr <http://structr.org>. * * Structr is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * Structr 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Structr. If not, see <http://www.gnu.org/licenses/>. */ package org.structr.ldap; import java.util.Collections; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.directory.api.ldap.model.constants.SchemaConstants; import org.apache.directory.server.core.entry.ClonedServerEntry; import org.apache.directory.shared.ldap.model.entry.Attribute; import org.apache.directory.shared.ldap.model.entry.DefaultAttribute; import org.apache.directory.shared.ldap.model.entry.DefaultEntry; import org.apache.directory.shared.ldap.model.entry.Entry; import org.apache.directory.shared.ldap.model.entry.Value; import org.apache.directory.shared.ldap.model.exception.LdapException; import org.apache.directory.shared.ldap.model.exception.LdapInvalidAttributeValueException; import org.apache.directory.shared.ldap.model.exception.LdapInvalidDnException; import org.apache.directory.shared.ldap.model.filter.AndNode; import org.apache.directory.shared.ldap.model.filter.AssertionType; import static org.apache.directory.shared.ldap.model.filter.AssertionType.EQUALITY; import org.apache.directory.shared.ldap.model.filter.ExprNode; import org.apache.directory.shared.ldap.model.filter.OrNode; import org.apache.directory.shared.ldap.model.filter.PresenceNode; import org.apache.directory.shared.ldap.model.filter.SimpleNode; import org.apache.directory.shared.ldap.model.filter.SubstringNode; import org.apache.directory.shared.ldap.model.message.SearchScope; import org.apache.directory.shared.ldap.model.name.Dn; import org.apache.directory.shared.ldap.model.name.Rdn; import org.apache.directory.shared.ldap.model.schema.AttributeType; import org.apache.directory.shared.ldap.model.schema.SchemaManager; import org.apache.directory.shared.ldap.model.schema.registries.ObjectClassRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.structr.common.SecurityContext; import org.structr.common.error.FrameworkException; import org.structr.core.app.App; import org.structr.core.app.StructrApp; import org.structr.core.entity.AbstractNode; import org.structr.core.graph.NodeAttribute; import org.structr.core.graph.Tx; import org.structr.ldap.api.LDAPAttribute; import org.structr.ldap.api.LDAPNode; import org.structr.ldap.api.LDAPValue; import org.structr.ldap.entity.LDAPNodeImpl; public class StructrLDAPWrapper { private static final Logger logger = LoggerFactory.getLogger(StructrLDAPWrapper.class.getName()); private SecurityContext securityContext = null; private SchemaManager schemaManager = null; private String partitionId = null; private Class<? extends LDAPNode> type = null; public StructrLDAPWrapper(final SecurityContext securityContext, final SchemaManager schemaManager, final String partitionId, final Class<? extends LDAPNode> type) { this.securityContext = securityContext; this.schemaManager = schemaManager; this.partitionId = partitionId; this.type = type; } public void add(final Entry entry) throws LdapException { try (final Tx tx = app().tx()) { // create while descending final Dn dn = entry.getDn(); final LDAPNode parent = find(dn.getParent()); if (parent != null) { final Attribute objectClasses = entry.get(schemaManager.getAttributeType(SchemaConstants.OBJECT_CLASS_AT_OID)); final ObjectClassRegistry reg = schemaManager.getObjectClassRegistry(); final Set<String> classes = new LinkedHashSet<>(); final Rdn rdn = dn.getRdn(); String mainClass = null; // make rdn schema aware if (!rdn.isSchemaAware()) { rdn.apply(schemaManager); } if (objectClasses != null) { for (final Value<?> value : objectClasses) { final String cls = value.getString(); final String objectClassOid = reg.getOidByName(cls); if (reg.get(objectClassOid).isStructural()) { mainClass = cls; } else { classes.add(cls); } } final LDAPNode newChild = parent.createChild(rdn.getNormName(), rdn.getName(), mainClass, classes); if (newChild != null) { for (final Attribute attr : entry) { AttributeType type = attr.getAttributeType(); String oid = null; if (type != null) { oid = type.getOid(); } else { type = schemaManager.getAttributeType(attr.getUpId()); oid = type.getOid(); } newChild.createAttribute(oid, attr.getUpId(), attr); } } else { logger.warn("Unable to add entry {}, could not create new instance", entry); } } else { logger.warn("Unable to add entry {}, could not determine object class(es)", entry); } } else { logger.warn("Unable to add entry {}, parent not found", entry); } tx.success(); } catch (FrameworkException fex) { handleException(fex); } } public Entry get(final Dn dn) throws LdapException { try (final Tx tx = app().tx()) { final LDAPNode entry = find(dn); tx.success(); if (entry != null) { return getEntry(entry); } } catch (FrameworkException fex) { handleException(fex); } // not found return null; } public void delete(final Dn dn) throws LdapException { final App app = app(); try (final Tx tx = app.tx()) { final LDAPNode entry = find(dn); if (entry != null) { entry.delete(); } tx.success(); } catch (FrameworkException fex) { handleException(fex); } } public List<Entry> filter(final Dn dn, final ExprNode filter, final SearchScope scope) throws LdapException { try (final Tx tx = app().tx()) { final LDAPNode entry = find(dn); List<Entry> result = null; if (entry != null) { result = filter(entry, filter, scope, 0); } tx.success(); if (result != null) { return result; } } catch (FrameworkException fex) { handleException(fex); } return Collections.emptyList(); } // ----- private methods ----- public Rdn getRdn(final LDAPNode node) throws FrameworkException, LdapInvalidDnException { String name = node.getUserProvidedName(); if (name == null) { name = node.getRdn(); } return new Rdn(schemaManager, name); } public Dn getDn(final LDAPNode node) throws FrameworkException, LdapInvalidDnException { final LDAPNode _parent = node.getParent(); if (_parent != null) { return new Dn(getRdn(node), getDn(_parent)); } return Dn.EMPTY_DN; } public Entry getEntry(final LDAPNode node) throws FrameworkException, LdapException { final DefaultEntry entry = new DefaultEntry(schemaManager, getDn(node)); for (final LDAPAttribute attr : node.getAttributes()) { entry.add(getAttribute(attr)); } return new ClonedServerEntry(entry); } public Attribute getAttribute(final LDAPAttribute src) throws LdapInvalidAttributeValueException { final AttributeType type = schemaManager.getAttributeType(src.getOid()); final Attribute attribute = new DefaultAttribute(type); final String name = src.getUserProvidedId(); if (name != null) { attribute.setUpId(name); } for (final LDAPValue value : src.getValues()) { attribute.add(value.getStringValue()); } return attribute; } private LDAPNode find(final Dn dn) throws FrameworkException, LdapException, LdapInvalidDnException { final List<Rdn> rdns = new LinkedList<>(dn.getRdns()); Collections.reverse(rdns); LDAPNode current = getRoot(); for (final Rdn rdn : rdns) { if (!rdn.isSchemaAware()) { rdn.apply(schemaManager); } current = current.getChild(rdn.getNormName()); // break early to avoid NPE if (current == null) { return null; } } return current; } private List<Entry> filter(final LDAPNode node, final ExprNode filter, final SearchScope scope, final int depth) throws FrameworkException, LdapException { final boolean base = SearchScope.OBJECT.equals(scope); final boolean oneLevel = SearchScope.ONELEVEL.equals(scope); final boolean subtree = SearchScope.SUBTREE.equals(scope); final List<Entry> list = new LinkedList<>(); if (base || !(depth == 0 && oneLevel)) { if (matches(node, filter)) { list.add(getEntry(node)); } } if (!base && (subtree || (depth == 0 && oneLevel))) { // recurse for (final LDAPNode child : node.getChildren()) { list.addAll(filter(child, filter, scope, depth + 1)); } } return list; } private boolean matches(final LDAPNode node, final ExprNode filter) throws FrameworkException, LdapInvalidAttributeValueException { if (filter instanceof SimpleNode) { return evaluateSimpleNode(node, (SimpleNode)filter); } else if (filter instanceof SubstringNode) { return evaluateSubstringNode(node, (SubstringNode)filter); } else if (filter instanceof PresenceNode) { final PresenceNode presence = (PresenceNode)filter; final Attribute attribute = new DefaultAttribute(presence.getAttributeType()); return findAttribute(node, attribute.getId()) != null; } else if (filter instanceof OrNode) { final OrNode orNode = (OrNode)filter; for (final ExprNode child : orNode.getChildren()) { if (matches(node, child)) { return true; } } return false; } else if (filter instanceof AndNode) { final AndNode andNode = (AndNode)filter; boolean result = true; for (final ExprNode child : andNode.getChildren()) { result &= matches(node, child); } return result; } else { System.out.println("Unsupported filter type " + filter.getClass()); } return false; } private boolean evaluateSimpleNode(final LDAPNode node, final SimpleNode simpleNode) throws FrameworkException, LdapInvalidAttributeValueException { final AssertionType assertionType = simpleNode.getAssertionType(); final Attribute attribute = new DefaultAttribute(simpleNode.getAttributeType(), simpleNode.getValue()); if (attribute != null) { switch (assertionType) { case EQUALITY: return hasAttributeValue(node, attribute); } } return false; } private boolean evaluateSubstringNode(final LDAPNode node, final SubstringNode substringNode) throws FrameworkException, LdapInvalidAttributeValueException { final Attribute attribute = new DefaultAttribute(substringNode.getAttributeType()); final String oid = attribute.getId(); final String initialPart = substringNode.getInitial(); final String finalPart = substringNode.getFinal(); final List<String> any = new LinkedList<>(); final List<String> fromNode = substringNode.getAny(); // add fragments from substring node (if present) if (fromNode != null) { any.addAll(fromNode); } final Pattern pattern = SubstringNode.getRegex(initialPart, any.toArray(new String[0]), finalPart); for (final LDAPAttribute attr : node.getAttributes()) { if (oid.equals(attr.getOid())) { for (final LDAPValue value : attr.getValues()) { final String stringValue = value.getStringValue().toLowerCase(); final Matcher matcher = pattern.matcher(stringValue); if (matcher.matches()) { return true; } } } } return false; } private boolean hasAttributeValue(final LDAPNode node, final Attribute value) throws FrameworkException, LdapInvalidAttributeValueException { final Attribute attribute = findAttribute(node, value.getId()); if (attribute != null) { return attribute.contains(value.get()); } return false; } private Attribute findAttribute(final LDAPNode node, final String oid) throws FrameworkException, LdapInvalidAttributeValueException { for (final LDAPAttribute attr : node.getAttributes()) { if (oid.equals(attr.getOid())) { return getAttribute(attr); } } return null; } private LDAPNode getRoot() throws FrameworkException { final App app = app(); LDAPNode root = (LDAPNode)app.nodeQuery(type).andName(partitionId).getFirst(); if (root == null) { root = app.create(LDAPNodeImpl.class, new NodeAttribute<>(AbstractNode.name, partitionId), new NodeAttribute<>(LDAPNodeImpl.isRoot, true) ); } return root; } private App app() { return StructrApp.getInstance(securityContext); } private void handleException(final FrameworkException fex) throws LdapException { logger.warn("", fex); } }