001/* 002 * Copyright 2017-2024 Ping Identity Corporation 003 * 004 * This program is free software; you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License (GPLv2 only) 006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 007 * as published by the Free Software Foundation. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program; if not, see <http://www.gnu.org/licenses>. 016 */ 017package com.unboundid.scim2.server.utils; 018 019import com.unboundid.scim2.common.Path; 020import com.unboundid.scim2.common.annotations.NotNull; 021import com.unboundid.scim2.common.annotations.Nullable; 022import com.unboundid.scim2.common.exceptions.ScimException; 023import com.unboundid.scim2.common.filters.AndFilter; 024import com.unboundid.scim2.common.filters.ComparisonFilter; 025import com.unboundid.scim2.common.filters.ComplexValueFilter; 026import com.unboundid.scim2.common.filters.ContainsFilter; 027import com.unboundid.scim2.common.filters.EndsWithFilter; 028import com.unboundid.scim2.common.filters.EqualFilter; 029import com.unboundid.scim2.common.filters.Filter; 030import com.unboundid.scim2.common.filters.FilterVisitor; 031import com.unboundid.scim2.common.filters.GreaterThanFilter; 032import com.unboundid.scim2.common.filters.GreaterThanOrEqualFilter; 033import com.unboundid.scim2.common.filters.LessThanFilter; 034import com.unboundid.scim2.common.filters.LessThanOrEqualFilter; 035import com.unboundid.scim2.common.filters.NotEqualFilter; 036import com.unboundid.scim2.common.filters.NotFilter; 037import com.unboundid.scim2.common.filters.OrFilter; 038import com.unboundid.scim2.common.filters.PresentFilter; 039import com.unboundid.scim2.common.filters.StartsWithFilter; 040import com.unboundid.scim2.common.types.AttributeDefinition; 041 042import java.util.ArrayList; 043import java.util.List; 044import java.util.Set; 045 046import static com.unboundid.scim2.server.utils.SchemaChecker.Option.ALLOW_UNDEFINED_ATTRIBUTES; 047import static com.unboundid.scim2.server.utils.SchemaChecker.Option.ALLOW_UNDEFINED_SUB_ATTRIBUTES; 048 049 050 051/** 052 * Filter visitor to check attribute paths against the schema. 053 */ 054public final class SchemaCheckFilterVisitor 055 implements FilterVisitor<Filter, Object> 056{ 057 @Nullable 058 private final Path parentPath; 059 060 @NotNull 061 private final ResourceTypeDefinition resourceType; 062 063 @NotNull 064 private final SchemaChecker schemaChecker; 065 066 @NotNull 067 private final SchemaChecker.Results results; 068 069 070 private SchemaCheckFilterVisitor( 071 @Nullable final Path parentPath, 072 @NotNull final ResourceTypeDefinition resourceType, 073 @NotNull final SchemaChecker schemaChecker, 074 @NotNull final SchemaChecker.Results results) 075 { 076 this.parentPath = parentPath; 077 this.resourceType = resourceType; 078 this.schemaChecker = schemaChecker; 079 this.results = results; 080 } 081 082 083 084 /** 085 * Check the provided filter against the schema. 086 * 087 * @param filter The filter to check. 088 * @param resourceTypeDefinition The schema to check the filter against. 089 * @param schemaChecker The object that will enforce schema constraints. 090 * @param enabledOptions The schema checker enabled options. 091 * @param results The results of checking the filter. 092 * @throws ScimException If an exception occurs during the operation. 093 */ 094 static void checkFilter( 095 @NotNull final Filter filter, 096 @NotNull final ResourceTypeDefinition resourceTypeDefinition, 097 @NotNull final SchemaChecker schemaChecker, 098 @NotNull final Set<SchemaChecker.Option> enabledOptions, 099 @NotNull final SchemaChecker.Results results) 100 throws ScimException 101 { 102 if (enabledOptions.contains(ALLOW_UNDEFINED_ATTRIBUTES) && 103 enabledOptions.contains(ALLOW_UNDEFINED_SUB_ATTRIBUTES)) 104 { 105 // Nothing to check because all undefined attributes are allowed. 106 return; 107 } 108 109 final SchemaCheckFilterVisitor visitor = 110 new SchemaCheckFilterVisitor( 111 null, resourceTypeDefinition, schemaChecker, results); 112 filter.visit(visitor, null); 113 } 114 115 116 117 /** 118 * Check the provided value filter in a patch path against the schema. 119 * 120 * @param parentPath The parent attribute associated with the value filter. 121 * @param filter The value filter to check. 122 * @param resourceTypeDefinition The schema to check the filter against. 123 * @param schemaChecker The object that will enforce schema constraints. 124 * @param enabledOptions The schema checker enabled options. 125 * @param results The results of checking the filter. 126 * @throws ScimException If an exception occurs during the operation. 127 */ 128 static void checkValueFilter( 129 @Nullable final Path parentPath, 130 @NotNull final Filter filter, 131 @NotNull final ResourceTypeDefinition resourceTypeDefinition, 132 @NotNull final SchemaChecker schemaChecker, 133 @NotNull final Set<SchemaChecker.Option> enabledOptions, 134 @NotNull final SchemaChecker.Results results) 135 throws ScimException 136 { 137 if (enabledOptions.contains(ALLOW_UNDEFINED_SUB_ATTRIBUTES)) 138 { 139 // Nothing to check because all undefined sub-attributes are allowed. 140 return; 141 } 142 143 final SchemaCheckFilterVisitor visitor = 144 new SchemaCheckFilterVisitor( 145 parentPath, resourceTypeDefinition, schemaChecker, results); 146 filter.visit(visitor, null); 147 } 148 149 150 151 /** 152 * {@inheritDoc} 153 */ 154 @NotNull 155 public Filter visit(@NotNull final EqualFilter filter, 156 @Nullable final Object param) 157 throws ScimException 158 { 159 return visitComparisonFilter(filter, param); 160 } 161 162 163 164 /** 165 * {@inheritDoc} 166 */ 167 @NotNull 168 public Filter visit(@NotNull final NotEqualFilter filter, 169 @Nullable final Object param) 170 throws ScimException 171 { 172 return visitComparisonFilter(filter, param); 173 } 174 175 176 177 /** 178 * {@inheritDoc} 179 */ 180 @NotNull 181 public Filter visit(@NotNull final ContainsFilter filter, 182 @Nullable final Object param) 183 throws ScimException 184 { 185 return visitComparisonFilter(filter, param); 186 } 187 188 189 190 /** 191 * {@inheritDoc} 192 */ 193 @NotNull 194 public Filter visit(@NotNull final StartsWithFilter filter, 195 @Nullable final Object param) 196 throws ScimException 197 { 198 return visitComparisonFilter(filter, param); 199 } 200 201 202 203 /** 204 * {@inheritDoc} 205 */ 206 @NotNull 207 public Filter visit(@NotNull final EndsWithFilter filter, 208 @Nullable final Object param) 209 throws ScimException 210 { 211 return visitComparisonFilter(filter, param); 212 } 213 214 215 216 /** 217 * {@inheritDoc} 218 */ 219 @NotNull 220 public Filter visit(@NotNull final PresentFilter filter, 221 @Nullable final Object param) 222 throws ScimException 223 { 224 checkAttributePath(filter.getAttributePath()); 225 return filter; 226 } 227 228 229 230 /** 231 * {@inheritDoc} 232 */ 233 @NotNull 234 public Filter visit(@NotNull final GreaterThanFilter filter, 235 @Nullable final Object param) 236 throws ScimException 237 { 238 return visitComparisonFilter(filter, param); 239 } 240 241 242 243 /** 244 * {@inheritDoc} 245 */ 246 @NotNull 247 public Filter visit(@NotNull final GreaterThanOrEqualFilter filter, 248 @Nullable final Object param) 249 throws ScimException 250 { 251 return visitComparisonFilter(filter, param); 252 } 253 254 255 256 /** 257 * {@inheritDoc} 258 */ 259 @NotNull 260 public Filter visit(@NotNull final LessThanFilter filter, 261 @Nullable final Object param) 262 throws ScimException 263 { 264 return visitComparisonFilter(filter, param); 265 } 266 267 268 269 /** 270 * {@inheritDoc} 271 */ 272 @NotNull 273 public Filter visit(@NotNull final LessThanOrEqualFilter filter, 274 @Nullable final Object param) 275 throws ScimException 276 { 277 return visitComparisonFilter(filter, param); 278 } 279 280 281 282 /** 283 * {@inheritDoc} 284 */ 285 @NotNull 286 public Filter visit(@NotNull final AndFilter filter, 287 @Nullable final Object param) 288 throws ScimException 289 { 290 for (Filter f : filter.getCombinedFilters()) 291 { 292 f.visit(this, param); 293 } 294 return filter; 295 } 296 297 298 299 /** 300 * {@inheritDoc} 301 */ 302 @NotNull 303 public Filter visit(@NotNull final OrFilter filter, 304 @Nullable final Object param) 305 throws ScimException 306 { 307 for (Filter f : filter.getCombinedFilters()) 308 { 309 f.visit(this, param); 310 } 311 return filter; 312 } 313 314 315 316 /** 317 * {@inheritDoc} 318 */ 319 @NotNull 320 public Filter visit(@NotNull final NotFilter filter, 321 @Nullable final Object param) 322 throws ScimException 323 { 324 filter.getInvertedFilter().visit(this, param); 325 return filter; 326 } 327 328 329 330 /** 331 * {@inheritDoc} 332 */ 333 @NotNull 334 public Filter visit(@NotNull final ComplexValueFilter filter, 335 @Nullable final Object param) 336 throws ScimException 337 { 338 checkAttributePath(filter.getAttributePath()); 339 return filter; 340 } 341 342 343 344 @NotNull 345 private Filter visitComparisonFilter(@NotNull final ComparisonFilter filter, 346 @Nullable final Object param) 347 { 348 checkAttributePath(filter.getAttributePath()); 349 return filter; 350 } 351 352 353 354 private void checkAttributePath(@NotNull final Path path) 355 { 356 if (this.parentPath != null) 357 { 358 final Path fullPath = parentPath.attribute(path); 359 final AttributeDefinition attribute = 360 resourceType.getAttributeDefinition(fullPath); 361 362 // Simple, multi-valued attributes implicitly use "value" as the 363 // name to access sub-attributes. Don't print the sub-attribute undefined 364 // error in this case. 365 if (path.getElement(0).getAttribute().equalsIgnoreCase("value")) 366 { 367 final AttributeDefinition parentAttr = 368 resourceType.getAttributeDefinition(parentPath); 369 if (parentAttr.isMultiValued() && 370 (parentAttr.getSubAttributes() == null)) 371 { 372 return; 373 } 374 } 375 376 if (attribute == null) 377 { 378 // Can't find the definition for the sub-attribute in a value filter. 379 results.addFilterIssue( 380 "Sub-attribute " + path.getElement(0) + 381 " in value filter for path " + parentPath.toString() + 382 " is undefined"); 383 } 384 } 385 else 386 { 387 final AttributeDefinition attribute = 388 resourceType.getAttributeDefinition(path); 389 if (attribute == null) 390 { 391 // Can't find the attribute definition for attribute in path. 392 final List<String> messages = new ArrayList<String>(); 393 schemaChecker.addMessageForUndefinedAttr(path, "", messages); 394 if (!messages.isEmpty()) 395 { 396 for (String m : messages) 397 { 398 results.addFilterIssue(m); 399 } 400 } 401 } 402 } 403 } 404}