001/* 002 * Copyright 2017-2018 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.exceptions.ScimException; 021import com.unboundid.scim2.common.filters.AndFilter; 022import com.unboundid.scim2.common.filters.ComparisonFilter; 023import com.unboundid.scim2.common.filters.ComplexValueFilter; 024import com.unboundid.scim2.common.filters.ContainsFilter; 025import com.unboundid.scim2.common.filters.EndsWithFilter; 026import com.unboundid.scim2.common.filters.EqualFilter; 027import com.unboundid.scim2.common.filters.Filter; 028import com.unboundid.scim2.common.filters.FilterVisitor; 029import com.unboundid.scim2.common.filters.GreaterThanFilter; 030import com.unboundid.scim2.common.filters.GreaterThanOrEqualFilter; 031import com.unboundid.scim2.common.filters.LessThanFilter; 032import com.unboundid.scim2.common.filters.LessThanOrEqualFilter; 033import com.unboundid.scim2.common.filters.NotEqualFilter; 034import com.unboundid.scim2.common.filters.NotFilter; 035import com.unboundid.scim2.common.filters.OrFilter; 036import com.unboundid.scim2.common.filters.PresentFilter; 037import com.unboundid.scim2.common.filters.StartsWithFilter; 038import com.unboundid.scim2.common.types.AttributeDefinition; 039 040import java.util.ArrayList; 041import java.util.List; 042import java.util.Set; 043 044import static com.unboundid.scim2.server.utils.SchemaChecker.Option.ALLOW_UNDEFINED_ATTRIBUTES; 045import static com.unboundid.scim2.server.utils.SchemaChecker.Option.ALLOW_UNDEFINED_SUB_ATTRIBUTES; 046 047 048 049/** 050 * Filter visitor to check attribute paths against the schema. 051 */ 052public final class SchemaCheckFilterVisitor 053 implements FilterVisitor<Filter, Object> 054{ 055 private final Path parentPath; 056 private final ResourceTypeDefinition resourceType; 057 private final SchemaChecker schemaChecker; 058 private final SchemaChecker.Results results; 059 060 061 private SchemaCheckFilterVisitor( 062 final Path parentPath, 063 final ResourceTypeDefinition resourceType, 064 final SchemaChecker schemaChecker, 065 final SchemaChecker.Results results) 066 { 067 this.parentPath = parentPath; 068 this.resourceType = resourceType; 069 this.schemaChecker = schemaChecker; 070 this.results = results; 071 } 072 073 074 075 /** 076 * Check the provided filter against the schema. 077 * 078 * @param filter The filter to check. 079 * @param resourceTypeDefinition The schema to check the filter against. 080 * @param enabledOptions The schema checker enabled options. 081 * @param results The results of checking the filter. 082 * @throws ScimException If an exception occurs during the operation. 083 */ 084 static void checkFilter( 085 final Filter filter, 086 final ResourceTypeDefinition resourceTypeDefinition, 087 final SchemaChecker schemaChecker, 088 final Set<SchemaChecker.Option> enabledOptions, 089 final SchemaChecker.Results results) 090 throws ScimException 091 { 092 if (enabledOptions.contains(ALLOW_UNDEFINED_ATTRIBUTES) && 093 enabledOptions.contains(ALLOW_UNDEFINED_SUB_ATTRIBUTES)) 094 { 095 // Nothing to check because all undefined attributes are allowed. 096 return; 097 } 098 099 final SchemaCheckFilterVisitor visitor = 100 new SchemaCheckFilterVisitor( 101 null, resourceTypeDefinition, schemaChecker, results); 102 filter.visit(visitor, null); 103 } 104 105 106 107 /** 108 * Check the provided value filter in a patch path against the schema. 109 * 110 * @param parentPath The parent attribute associated with the value filter. 111 * @param filter The value filter to check. 112 * @param resourceTypeDefinition The schema to check the filter against. 113 * @param enabledOptions The schema checker enabled options. 114 * @param results The results of checking the filter. 115 * @throws ScimException If an exception occurs during the operation. 116 */ 117 static void checkValueFilter( 118 final Path parentPath, 119 final Filter filter, 120 final ResourceTypeDefinition resourceTypeDefinition, 121 final SchemaChecker schemaChecker, 122 final Set<SchemaChecker.Option> enabledOptions, 123 final SchemaChecker.Results results) 124 throws ScimException 125 { 126 if (enabledOptions.contains(ALLOW_UNDEFINED_SUB_ATTRIBUTES)) 127 { 128 // Nothing to check because all undefined sub-attributes are allowed. 129 return; 130 } 131 132 final SchemaCheckFilterVisitor visitor = 133 new SchemaCheckFilterVisitor( 134 parentPath, resourceTypeDefinition, schemaChecker, results); 135 filter.visit(visitor, null); 136 } 137 138 139 140 /** 141 * {@inheritDoc} 142 */ 143 public Filter visit(final EqualFilter filter, final Object param) 144 throws ScimException 145 { 146 return visitComparisonFilter(filter, param); 147 } 148 149 150 151 /** 152 * {@inheritDoc} 153 */ 154 public Filter visit(final NotEqualFilter filter, final Object param) 155 throws ScimException 156 { 157 return visitComparisonFilter(filter, param); 158 } 159 160 161 162 /** 163 * {@inheritDoc} 164 */ 165 public Filter visit(final ContainsFilter filter, final Object param) 166 throws ScimException 167 { 168 return visitComparisonFilter(filter, param); 169 } 170 171 172 173 /** 174 * {@inheritDoc} 175 */ 176 public Filter visit(final StartsWithFilter filter, final Object param) 177 throws ScimException 178 { 179 return visitComparisonFilter(filter, param); 180 } 181 182 183 184 /** 185 * {@inheritDoc} 186 */ 187 public Filter visit(final EndsWithFilter filter, final Object param) 188 throws ScimException 189 { 190 return visitComparisonFilter(filter, param); 191 } 192 193 194 195 /** 196 * {@inheritDoc} 197 */ 198 public Filter visit(final PresentFilter filter, final Object param) 199 throws ScimException 200 { 201 checkAttributePath(filter.getAttributePath()); 202 return filter; 203 } 204 205 206 207 /** 208 * {@inheritDoc} 209 */ 210 public Filter visit(final GreaterThanFilter filter, final Object param) 211 throws ScimException 212 { 213 return visitComparisonFilter(filter, param); 214 } 215 216 217 218 /** 219 * {@inheritDoc} 220 */ 221 public Filter visit(final GreaterThanOrEqualFilter filter, final Object param) 222 throws ScimException 223 { 224 return visitComparisonFilter(filter, param); 225 } 226 227 228 229 /** 230 * {@inheritDoc} 231 */ 232 public Filter visit(final LessThanFilter filter, final Object param) 233 throws ScimException 234 { 235 return visitComparisonFilter(filter, param); 236 } 237 238 239 240 /** 241 * {@inheritDoc} 242 */ 243 public Filter visit(final LessThanOrEqualFilter filter, final Object param) 244 throws ScimException 245 { 246 return visitComparisonFilter(filter, param); 247 } 248 249 250 251 /** 252 * {@inheritDoc} 253 */ 254 public Filter visit(final AndFilter filter, final Object param) 255 throws ScimException 256 { 257 for (Filter f : filter.getCombinedFilters()) 258 { 259 f.visit(this, param); 260 } 261 return filter; 262 } 263 264 265 266 /** 267 * {@inheritDoc} 268 */ 269 public Filter visit(final OrFilter filter, final Object param) 270 throws ScimException 271 { 272 for (Filter f : filter.getCombinedFilters()) 273 { 274 f.visit(this, param); 275 } 276 return filter; 277 } 278 279 280 281 /** 282 * {@inheritDoc} 283 */ 284 public Filter visit(final NotFilter filter, final Object param) 285 throws ScimException 286 { 287 filter.getInvertedFilter().visit(this, param); 288 return filter; 289 } 290 291 292 293 /** 294 * {@inheritDoc} 295 */ 296 public Filter visit(final ComplexValueFilter filter, final Object param) 297 throws ScimException 298 { 299 checkAttributePath(filter.getAttributePath()); 300 return filter; 301 } 302 303 304 305 private Filter visitComparisonFilter(final ComparisonFilter filter, 306 final Object param) 307 { 308 checkAttributePath(filter.getAttributePath()); 309 return filter; 310 } 311 312 313 314 private void checkAttributePath(final Path path) 315 { 316 if (this.parentPath != null) 317 { 318 final Path fullPath = parentPath.attribute(path); 319 final AttributeDefinition attribute = 320 resourceType.getAttributeDefinition(fullPath); 321 322 // Simple, multi-valued attributes implicitly use "value" as the 323 // name to access sub-attributes. Don't print the sub-attribute undefined 324 // error in this case. 325 if (path.getElement(0).getAttribute().equalsIgnoreCase("value")) 326 { 327 final AttributeDefinition parentAttr = 328 resourceType.getAttributeDefinition(parentPath); 329 if (parentAttr.isMultiValued() && 330 (parentAttr.getSubAttributes() == null)) 331 { 332 return; 333 } 334 } 335 336 if (attribute == null) 337 { 338 // Can't find the definition for the sub-attribute in a value filter. 339 results.addFilterIssue( 340 "Sub-attribute " + path.getElement(0) + 341 " in value filter for path " + parentPath.toString() + 342 " is undefined"); 343 } 344 } 345 else 346 { 347 final AttributeDefinition attribute = 348 resourceType.getAttributeDefinition(path); 349 if (attribute == null) 350 { 351 // Can't find the attribute definition for attribute in path. 352 final List<String> messages = new ArrayList<String>(); 353 schemaChecker.addMessageForUndefinedAttr(path, "", messages); 354 if (!messages.isEmpty()) 355 { 356 for (String m : messages) 357 { 358 results.addFilterIssue(m); 359 } 360 } 361 } 362 } 363 } 364}