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.Arrays; 023import java.util.Iterator; 024import java.util.List; 025import java.util.TreeMap; 026 027import org.forgerock.i18n.LocalizableMessage; 028import org.forgerock.i18n.slf4j.LocalizedLogger; 029import org.forgerock.opendj.ldap.AVA; 030import org.forgerock.opendj.ldap.ByteString; 031import org.forgerock.opendj.ldap.DecodeException; 032import org.forgerock.opendj.ldap.RDN; 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; 040 041/** 042 * This class is used to match RDN patterns containing wildcards in either 043 * the attribute types or the attribute values. 044 * Substring matching on the attribute types is not supported. 045 */ 046public class PatternRDN 047{ 048 049 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 050 051 /** Indicate whether the RDN contains a wildcard in any of its attribute types. */ 052 private final boolean hasTypeWildcard; 053 /** The set of attribute type patterns. */ 054 private String[] typePatterns; 055 /** 056 * The set of attribute value patterns. 057 * The value pattern is split into a list according to the positions of any 058 * wildcards. For example, the value "A*B*C" is represented as a 059 * list of three elements A, B and C. The value "A" is represented as 060 * a list of one element A. The value "*A*" is represented as a list 061 * of three elements "", A and "". 062 */ 063 private final List<List<ByteString>> valuePatterns; 064 065 /** 066 * Create a new RDN pattern composed of a single attribute-value pair. 067 * @param type The attribute type pattern. 068 * @param valuePattern The attribute value pattern. 069 * @param dnString The DN pattern containing the attribute-value pair. 070 * @throws DirectoryException If the attribute-value pair is not valid. 071 */ 072 public PatternRDN(String type, List<ByteString> valuePattern, String dnString) 073 throws DirectoryException 074 { 075 // Only Whole-Type wildcards permitted. 076 if (type.contains("*")) 077 { 078 if (!type.equals("*")) 079 { 080 LocalizableMessage message = 081 WARN_PATTERN_DN_TYPE_CONTAINS_SUBSTRINGS.get(dnString); 082 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 083 message); 084 } 085 hasTypeWildcard = true; 086 } 087 else 088 { 089 hasTypeWildcard = false; 090 } 091 092 typePatterns = new String[] { type }; 093 valuePatterns = newArrayList(valuePattern); 094 } 095 096 097 /** 098 * Add another attribute-value pair to the pattern. 099 * @param type The attribute type pattern. 100 * @param valuePattern The attribute value pattern. 101 * @param dnString The DN pattern containing the attribute-value pair. 102 * @throws DirectoryException If the attribute-value pair is not valid. 103 * @return <CODE>true</CODE> if the type-value pair was added to 104 * this RDN, or <CODE>false</CODE> if it was not (e.g., it 105 * was already present). 106 */ 107 public boolean addValue(String type, List<ByteString> valuePattern, String dnString) throws DirectoryException 108 { 109 // No type wildcards permitted in multi-valued patterns. 110 if (hasTypeWildcard || type.contains("*")) 111 { 112 LocalizableMessage message = 113 WARN_PATTERN_DN_TYPE_WILDCARD_IN_MULTIVALUED_RDN.get(dnString); 114 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); 115 } 116 117 int oldLength = typePatterns.length; 118 typePatterns = Arrays.copyOf(typePatterns, oldLength + 1); 119 typePatterns[oldLength] = type; 120 121 valuePatterns.add(valuePattern); 122 123 return true; 124 } 125 126 127 /** 128 * Retrieves the number of attribute-value pairs contained in this 129 * RDN pattern. 130 * 131 * @return The number of attribute-value pairs contained in this 132 * RDN pattern. 133 */ 134 public int getNumValues() 135 { 136 return typePatterns.length; 137 } 138 139 140 /** 141 * Determine whether a given RDN matches the pattern. 142 * @param rdn The RDN to be matched. 143 * @return true if the RDN matches the pattern. 144 */ 145 public boolean matchesRDN(RDN rdn) 146 { 147 if (getNumValues() == 1) 148 { 149 // Check for ",*," matching any RDN. 150 if (typePatterns[0].equals("*") && valuePatterns.get(0) == null) 151 { 152 return true; 153 } 154 155 if (rdn.size() != 1) 156 { 157 return false; 158 } 159 160 AVA ava = rdn.getFirstAVA(); 161 if (!typePatterns[0].equals("*")) 162 { 163 AttributeType thisType = DirectoryServer.getSchema().getAttributeType(typePatterns[0]); 164 if (thisType.isPlaceHolder() || !thisType.equals(ava.getAttributeType())) 165 { 166 return false; 167 } 168 } 169 170 return matchValuePattern(valuePatterns.get(0), ava); 171 } 172 173 if (hasTypeWildcard || typePatterns.length != rdn.size()) 174 { 175 return false; 176 } 177 178 // Sort the attribute-value pairs by attribute type. 179 TreeMap<String, List<ByteString>> patternMap = new TreeMap<>(); 180 for (int i = 0; i < typePatterns.length; i++) 181 { 182 AttributeType type = DirectoryServer.getSchema().getAttributeType(typePatterns[i]); 183 if (type.isPlaceHolder()) 184 { 185 return false; 186 } 187 patternMap.put(type.getNameOrOID(), valuePatterns.get(i)); 188 } 189 190 Iterator<String> patternKeyIter = patternMap.keySet().iterator(); 191 for (AVA ava : rdn) 192 { 193 String rdnKey = ava.getAttributeType().getNameOrOID(); 194 if (!rdnKey.equals(patternKeyIter.next()) 195 || !matchValuePattern(patternMap.get(rdnKey), ava)) 196 { 197 return false; 198 } 199 } 200 201 return true; 202 } 203 204 /** 205 * Determine whether a value pattern matches a given attribute-value pair. 206 * @param pattern The value pattern where each element of the list is a 207 * substring of the pattern appearing between wildcards. 208 * @param type The attribute type of the attribute-value pair. 209 * @param value The value of the attribute-value pair. 210 * @return true if the value pattern matches the attribute-value pair. 211 */ 212 private boolean matchValuePattern(List<ByteString> pattern, AVA ava) 213 { 214 if (pattern == null) 215 { 216 return true; 217 } 218 219 final AttributeType type = ava.getAttributeType(); 220 ByteString value = ava.getAttributeValue(); 221 try 222 { 223 if (pattern.size() == 1) 224 { 225 // Handle this just like an equality filter. 226 MatchingRule rule = type.getEqualityMatchingRule(); 227 ByteString thatNormValue = rule.normalizeAttributeValue(value); 228 return rule.getAssertion(pattern.get(0)).matches(thatNormValue).toBoolean(); 229 } 230 231 // Handle this just like a substring filter. 232 ByteString subInitial = pattern.get(0); 233 if (subInitial.length() == 0) 234 { 235 subInitial = null; 236 } 237 238 ByteString subFinal = pattern.get(pattern.size() - 1); 239 if (subFinal.length() == 0) 240 { 241 subFinal = null; 242 } 243 244 List<ByteString> subAnyElements; 245 if (pattern.size() > 2) 246 { 247 subAnyElements = pattern.subList(1, pattern.size()-1); 248 } 249 else 250 { 251 subAnyElements = null; 252 } 253 254 Attribute attr = Attributes.create(type, value); 255 return attr.matchesSubstring(subInitial, subAnyElements, subFinal).toBoolean(); 256 } 257 catch (DecodeException e) 258 { 259 logger.traceException(e); 260 return false; 261 } 262 } 263 264 @Override 265 public String toString() 266 { 267 StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append("("); 268 for (int i = 0; i < typePatterns.length; i++) 269 { 270 sb.append(typePatterns[i]).append("="); 271 List<ByteString> patterns = valuePatterns.get(i); 272 if (patterns.size() == 1) 273 { 274 sb.append(patterns.get(0)); 275 } 276 else 277 { 278 sb.append(patterns); 279 } 280 } 281 sb.append(")"); 282 return sb.toString(); 283 } 284}