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}