/* * Password Management Servlets (PWM) * http://www.pwm-project.org * * Copyright (c) 2006-2009 Novell, Inc. * Copyright (c) 2009-2017 The PWM Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package password.pwm.ldap.schema; import com.novell.ldap.client.SchemaParser; import com.novell.ldapchai.ChaiEntry; import com.novell.ldapchai.ChaiFactory; import com.novell.ldapchai.exception.ChaiOperationException; import com.novell.ldapchai.exception.ChaiUnavailableException; import com.novell.ldapchai.provider.ChaiProvider; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmUnrecoverableException; import password.pwm.util.java.JsonUtil; import password.pwm.util.logging.PwmLogger; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.TreeSet; public class EdirSchemaExtender implements SchemaExtender { private static final PwmLogger LOGGER = PwmLogger.forClass(EdirSchemaExtender.class); private static final String LDAP_SCHEMA_DN = "cn=schema"; private static final String LDAP_SCHEMA_ATTR_ATTRS = "attributeTypes"; private static final String LDAP_SCHEMA_ATTR_CLASSES = "objectClasses"; private ChaiEntry schemaEntry; private final StringBuilder activityLog = new StringBuilder(); private final Map<String,SchemaDefinition.State> stateMap = new HashMap(); public void init(final ChaiProvider chaiProvider) throws PwmUnrecoverableException { try { schemaEntry = ChaiFactory.createChaiEntry(LDAP_SCHEMA_DN, chaiProvider); } catch (ChaiUnavailableException e) { throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage())); } } @Override public SchemaOperationResult extendSchema() throws PwmUnrecoverableException { activityLog.delete(0,activityLog.length()); execute(false); return new SchemaOperationResult(allStatesCorrect(),getActivityLog()); } @Override public SchemaOperationResult checkExistingSchema() throws PwmUnrecoverableException { execute(true); return new SchemaOperationResult(allStatesCorrect(),getActivityLog()); } private boolean allStatesCorrect() { boolean allStatesCorrect = true; for (final String key : stateMap.keySet()) { if (SchemaDefinition.State.correct != stateMap.get(key)) { allStatesCorrect = false; } } return allStatesCorrect; } private void execute(final boolean readOnly) throws PwmUnrecoverableException { activityLog.delete(0,activityLog.length()); logActivity("connecting to " + schemaEntry.getChaiProvider().getChaiConfiguration().bindURLsAsList().iterator().next()); stateMap.clear(); try { final Map<String, SchemaParser> existingAttrs = readSchemaAttributes(); for (final SchemaDefinition schemaDefinition : SchemaDefinition.getPwmSchemaDefinitions()) { if (schemaDefinition.getSchemaType() == SchemaDefinition.SchemaType.attribute) { checkAttribute(readOnly, schemaDefinition, existingAttrs); } } final Map<String, SchemaParser> existingObjectclasses = readSchemaObjectclasses(); for (final SchemaDefinition schemaDefinition : SchemaDefinition.getPwmSchemaDefinitions()) { if (schemaDefinition.getSchemaType() == SchemaDefinition.SchemaType.objectclass) { checkObjectclass(readOnly, schemaDefinition, existingObjectclasses); } } } catch (ChaiUnavailableException e) { throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE,e.getMessage())); } catch (ChaiOperationException e) { throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN,e.getMessage())); } } private void checkObjectclass(final boolean readOnly, final SchemaDefinition schemaDefinition, final Map<String, SchemaParser> existingAttrs) throws ChaiUnavailableException { final String name = schemaDefinition.getName(); if (existingAttrs.containsKey(name)) { final SchemaParser existingValue = existingAttrs.get(name); logActivity("objectclass '" + name + "' exists"); final boolean objectclassIsCorrect = checkObjectclassCorrectness(schemaDefinition, existingValue); stateMap.put(name, objectclassIsCorrect ? SchemaDefinition.State.correct : SchemaDefinition.State.incorrect); if (!readOnly && !objectclassIsCorrect) { logActivity("beginning update for objectclass '" + name + "'"); try { schemaEntry.replaceAttribute(LDAP_SCHEMA_ATTR_CLASSES, existingValue.getRawString(), schemaDefinition.getDefinition()); logActivity("+ objectclass '" + name + "' has been modified"); stateMap.put(name, SchemaDefinition.State.correct); } catch (ChaiOperationException e) { logActivity("error while updating objectclass definition '" + name + "', error: " + e.getMessage()); } } } else { logActivity("objectclass '" + name + "' does not exist"); stateMap.put(name, SchemaDefinition.State.missing); if (!readOnly) { logActivity("beginning add for objectclass '" + name + "'"); try { schemaEntry.addAttribute(LDAP_SCHEMA_ATTR_CLASSES, schemaDefinition.getDefinition()); logActivity("+ objectclass '" + name + "' has been added"); stateMap.put(name, SchemaDefinition.State.correct); } catch (ChaiOperationException e) { logActivity("error while updating objectclass definition '" + name + "', error: " + e.getMessage()); } } } } private void checkAttribute(final boolean readOnly, final SchemaDefinition schemaDefinition, final Map<String, SchemaParser> existingAttrs) throws ChaiUnavailableException { final String name = schemaDefinition.getName(); if (existingAttrs.containsKey(name)) { final SchemaParser existingValue = existingAttrs.get(name); logActivity("attribute '" + name + "' exists"); final boolean attributeIsCorrect = checkAttributeCorrectness(schemaDefinition, existingValue ); stateMap.put(name, attributeIsCorrect ? SchemaDefinition.State.correct : SchemaDefinition.State.incorrect); if (!readOnly && !attributeIsCorrect) { logActivity("beginning update for attribute '" + name + "'"); try { schemaEntry.replaceAttribute(LDAP_SCHEMA_ATTR_ATTRS, existingValue.getRawString(), schemaDefinition.getDefinition()); logActivity("+ attribute '" + name + "' has been modified"); stateMap.put(name, SchemaDefinition.State.correct); } catch (ChaiOperationException e) { logActivity("error while updating attribute definition '" + name + "', error: " + e.getMessage()); } } } else { logActivity("attribute '" + name + "' does not exist"); if (!readOnly) { logActivity("beginning add for attribute '" + name + "'"); try { schemaEntry.addAttribute(LDAP_SCHEMA_ATTR_ATTRS, schemaDefinition.getDefinition()); stateMap.put(name, SchemaDefinition.State.missing); logActivity("+ attribute '" + name + "' has been added"); stateMap.put(name, SchemaDefinition.State.correct); } catch (ChaiOperationException e) { logActivity("error while adding attribute definition '" + name + "', error: " + e.getMessage()); } } } } private boolean checkObjectclassCorrectness(final SchemaDefinition schemaDefinition, final SchemaParser existingAttr) { boolean checkPassed = true; try { final SchemaParser schemaDef = new SchemaParser(schemaDefinition.getDefinition()); { final String defId = schemaDef.getID(); final String existingID = existingAttr.getID(); if (defId != null && !defId.equals(existingID)) { logActivity("objectclass '" + schemaDefinition.getName() + "' ID (" + existingID + ") is not correct, correct ID is (" + defId + ")"); checkPassed = false; } } { final Set<String> defOptionals = new TreeSet<>(Arrays.asList(schemaDef.getOptional())); final Set<String> existingOptionals = new TreeSet<>(Arrays.asList(existingAttr.getOptional())); if (!defOptionals.equals(existingOptionals)) { logActivity("objectclass '" + schemaDefinition.getName() + "' optional attributes (" + JsonUtil.serializeCollection(defOptionals) + ") is not correct, correct optional attribute list is (" + JsonUtil.serializeCollection(existingOptionals)+ ")"); checkPassed = false; } } } catch (IOException e) { e.printStackTrace(); } return checkPassed; } private boolean checkAttributeCorrectness(final SchemaDefinition schemaDefinition, final SchemaParser existingAttr) { boolean checkPassed = true; try { final SchemaParser schemaDef = new SchemaParser(schemaDefinition.getDefinition()); { final String defId = schemaDef.getID(); final String existingID = existingAttr.getID(); if (defId != null && !defId.equals(existingID)) { logActivity("attribute '" + schemaDefinition.getName() + "' ID (" + existingID + ") is not correct, correct ID is (" + defId + ")"); checkPassed = false; } } { final String defSyntax = normalizeSyntaxID(schemaDef.getSyntax()); final String existingSyntax = normalizeSyntaxID(existingAttr.getSyntax()); if (defSyntax != null && !defSyntax.equals(existingSyntax)) { logActivity("attribute '" + schemaDefinition.getName() + "' syntax (" + existingSyntax + ") is not correct, correct syntax is (" + defSyntax + ")"); checkPassed = false; } } } catch (IOException e) { e.printStackTrace(); } return checkPassed; } private Map<String,SchemaParser> readSchemaAttributes() throws ChaiUnavailableException, ChaiOperationException { final Map<String,SchemaParser> returnObj = new LinkedHashMap<>(); final Set<String> valuesFromLdap = schemaEntry.readMultiStringAttribute(LDAP_SCHEMA_ATTR_ATTRS); for (final String key : valuesFromLdap) { SchemaParser schemaParser = null; try { schemaParser = new SchemaParser(key); } catch (Exception e) { LOGGER.error("error parsing schema attribute definition: " + e.getMessage()); } if (schemaParser != null) { for (final String attrName : schemaParser.getNames()) { returnObj.put(attrName, schemaParser); } } } return returnObj; } private Map<String,SchemaParser> readSchemaObjectclasses() throws ChaiUnavailableException, ChaiOperationException { final Map<String,SchemaParser> returnObj = new LinkedHashMap<>(); final Set<String> valuesFromLdap = schemaEntry.readMultiStringAttribute(LDAP_SCHEMA_ATTR_CLASSES); for (final String key : valuesFromLdap) { SchemaParser schemaParser = null; try { schemaParser = new SchemaParser(key); } catch (Exception e) { LOGGER.error("error parsing schema objectclasses definition: " + e.getMessage()); } if (schemaParser != null) { for (final String attrName : schemaParser.getNames()) { returnObj.put(attrName, schemaParser); } } } return returnObj; } private void logActivity(final CharSequence charSequence) { LOGGER.info(charSequence); activityLog.append(charSequence).append("\n"); } private String getActivityLog() { return activityLog.toString(); } private String normalizeSyntaxID(final String input) { return input == null ? "" : input.replaceFirst("\\{[0-9]+\\}$",""); } }