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