001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2008 Sun Microsystems, Inc. 025 * Portions Copyright 2014-2015 ForgeRock AS 026 */ 027package org.opends.server.authorization.dseecompat; 028 029import static org.opends.messages.AccessControlMessages.*; 030import static org.opends.server.util.CollectionUtils.*; 031 032import java.util.ArrayList; 033import java.util.Iterator; 034import java.util.List; 035import java.util.Set; 036import java.util.TreeMap; 037 038import org.forgerock.i18n.LocalizableMessage; 039import org.forgerock.i18n.slf4j.LocalizedLogger; 040import org.forgerock.opendj.ldap.ByteString; 041import org.forgerock.opendj.ldap.DecodeException; 042import org.forgerock.opendj.ldap.ResultCode; 043import org.forgerock.opendj.ldap.schema.MatchingRule; 044import org.opends.server.core.DirectoryServer; 045import org.opends.server.types.*; 046 047/** 048 * This class is used to match RDN patterns containing wildcards in either 049 * the attribute types or the attribute values. 050 * Substring matching on the attribute types is not supported. 051 */ 052public class PatternRDN 053{ 054 055 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 056 057 /** Indicate whether the RDN contains a wildcard in any of its attribute types. */ 058 private boolean hasTypeWildcard; 059 /** The set of attribute type patterns. */ 060 private String[] typePatterns; 061 /** 062 * The set of attribute value patterns. 063 * The value pattern is split into a list according to the positions of any 064 * wildcards. For example, the value "A*B*C" is represented as a 065 * list of three elements A, B and C. The value "A" is represented as 066 * a list of one element A. The value "*A*" is represented as a list 067 * of three elements "", A and "". 068 */ 069 private ArrayList<ArrayList<ByteString>> valuePatterns; 070 /** The number of attribute-value pairs in this RDN pattern. */ 071 private int numValues; 072 073 074 /** 075 * Create a new RDN pattern composed of a single attribute-value pair. 076 * @param type The attribute type pattern. 077 * @param valuePattern The attribute value pattern. 078 * @param dnString The DN pattern containing the attribute-value pair. 079 * @throws DirectoryException If the attribute-value pair is not valid. 080 */ 081 public PatternRDN(String type, ArrayList<ByteString> valuePattern, String dnString) 082 throws DirectoryException 083 { 084 // Only Whole-Type wildcards permitted. 085 if (type.contains("*")) 086 { 087 if (!type.equals("*")) 088 { 089 LocalizableMessage message = 090 WARN_PATTERN_DN_TYPE_CONTAINS_SUBSTRINGS.get(dnString); 091 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 092 message); 093 } 094 hasTypeWildcard = true; 095 } 096 097 numValues = 1; 098 typePatterns = new String[] { type }; 099 valuePatterns = newArrayList(valuePattern); 100 } 101 102 103 /** 104 * Add another attribute-value pair to the pattern. 105 * @param type The attribute type pattern. 106 * @param valuePattern The attribute value pattern. 107 * @param dnString The DN pattern containing the attribute-value pair. 108 * @throws DirectoryException If the attribute-value pair is not valid. 109 * @return <CODE>true</CODE> if the type-value pair was added to 110 * this RDN, or <CODE>false</CODE> if it was not (e.g., it 111 * was already present). 112 */ 113 public boolean addValue(String type, ArrayList<ByteString> valuePattern, 114 String dnString) 115 throws DirectoryException 116 { 117 // No type wildcards permitted in multi-valued patterns. 118 if (hasTypeWildcard || type.contains("*")) 119 { 120 LocalizableMessage message = 121 WARN_PATTERN_DN_TYPE_WILDCARD_IN_MULTIVALUED_RDN.get(dnString); 122 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); 123 } 124 125 numValues++; 126 127 String[] newTypes = new String[numValues]; 128 System.arraycopy(typePatterns, 0, newTypes, 0, 129 typePatterns.length); 130 newTypes[typePatterns.length] = type; 131 typePatterns = newTypes; 132 133 valuePatterns.add(valuePattern); 134 135 return true; 136 } 137 138 139 /** 140 * Retrieves the number of attribute-value pairs contained in this 141 * RDN pattern. 142 * 143 * @return The number of attribute-value pairs contained in this 144 * RDN pattern. 145 */ 146 public int getNumValues() 147 { 148 return numValues; 149 } 150 151 152 /** 153 * Determine whether a given RDN matches the pattern. 154 * @param rdn The RDN to be matched. 155 * @return true if the RDN matches the pattern. 156 */ 157 public boolean matchesRDN(RDN rdn) 158 { 159 if (getNumValues() == 1) 160 { 161 // Check for ",*," matching any RDN. 162 if (typePatterns[0].equals("*") && valuePatterns.get(0) == null) 163 { 164 return true; 165 } 166 167 if (rdn.getNumValues() != 1) 168 { 169 return false; 170 } 171 172 AttributeType thatType = rdn.getAttributeType(0); 173 if (!typePatterns[0].equals("*")) 174 { 175 AttributeType thisType = DirectoryServer.getAttributeTypeOrNull(typePatterns[0].toLowerCase()); 176 if (thisType == null || !thisType.equals(thatType)) 177 { 178 return false; 179 } 180 } 181 182 return matchValuePattern(valuePatterns.get(0), thatType, rdn.getAttributeValue(0)); 183 } 184 185 if (hasTypeWildcard) 186 { 187 return false; 188 } 189 190 if (numValues != rdn.getNumValues()) 191 { 192 return false; 193 } 194 195 // Sort the attribute-value pairs by attribute type. 196 TreeMap<String,ArrayList<ByteString>> patternMap = new TreeMap<>(); 197 TreeMap<String, ByteString> rdnMap = new TreeMap<>(); 198 199 for (int i = 0; i < rdn.getNumValues(); i++) 200 { 201 rdnMap.put(rdn.getAttributeType(i).getNameOrOID(), 202 rdn.getAttributeValue(i)); 203 } 204 205 for (int i = 0; i < numValues; i++) 206 { 207 String lowerName = typePatterns[i].toLowerCase(); 208 AttributeType type = DirectoryServer.getAttributeTypeOrNull(lowerName); 209 if (type == null) 210 { 211 return false; 212 } 213 patternMap.put(type.getNameOrOID(), valuePatterns.get(i)); 214 } 215 216 Set<String> patternKeys = patternMap.keySet(); 217 Set<String> rdnKeys = rdnMap.keySet(); 218 Iterator<String> patternKeyIter = patternKeys.iterator(); 219 for (String rdnKey : rdnKeys) 220 { 221 if (!rdnKey.equals(patternKeyIter.next())) 222 { 223 return false; 224 } 225 226 AttributeType rdnAttrType = DirectoryServer.getAttributeTypeOrNull(rdnKey); 227 if (!matchValuePattern(patternMap.get(rdnKey), rdnAttrType, rdnMap.get(rdnKey))) 228 { 229 return false; 230 } 231 } 232 233 return true; 234 } 235 236 237 /** 238 * Determine whether a value pattern matches a given attribute-value pair. 239 * @param pattern The value pattern where each element of the list is a 240 * substring of the pattern appearing between wildcards. 241 * @param type The attribute type of the attribute-value pair. 242 * @param value The value of the attribute-value pair. 243 * @return true if the value pattern matches the attribute-value pair. 244 */ 245 private boolean matchValuePattern(List<ByteString> pattern, 246 AttributeType type, 247 ByteString value) 248 { 249 if (pattern == null) 250 { 251 return true; 252 } 253 254 try 255 { 256 if (pattern.size() == 1) 257 { 258 // Handle this just like an equality filter. 259 MatchingRule rule = type.getEqualityMatchingRule(); 260 ByteString thatNormValue = rule.normalizeAttributeValue(value); 261 return rule.getAssertion(pattern.get(0)).matches(thatNormValue).toBoolean(); 262 } 263 264 // Handle this just like a substring filter. 265 ByteString subInitial = pattern.get(0); 266 if (subInitial.length() == 0) 267 { 268 subInitial = null; 269 } 270 271 ByteString subFinal = pattern.get(pattern.size() - 1); 272 if (subFinal.length() == 0) 273 { 274 subFinal = null; 275 } 276 277 List<ByteString> subAnyElements; 278 if (pattern.size() > 2) 279 { 280 subAnyElements = pattern.subList(1, pattern.size()-1); 281 } 282 else 283 { 284 subAnyElements = null; 285 } 286 287 Attribute attr = Attributes.create(type, value); 288 return attr.matchesSubstring(subInitial, subAnyElements, subFinal).toBoolean(); 289 } 290 catch (DecodeException e) 291 { 292 logger.traceException(e); 293 return false; 294 } 295 } 296 297}