001/*
002 * Copyright 2015-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 */
017
018package com.unboundid.scim2.server.utils;
019
020import com.unboundid.scim2.common.Path;
021import com.unboundid.scim2.common.annotations.NotNull;
022import com.unboundid.scim2.common.types.AttributeDefinition;
023
024import java.util.Set;
025
026
027
028/**
029 * A resource trimmer implementing the SCIM standard for returning attributes.
030 */
031public class ScimResourceTrimmer extends ResourceTrimmer
032{
033  @NotNull
034  private final ResourceTypeDefinition resourceType;
035
036  @NotNull
037  private final Set<Path> requestAttributes;
038
039  @NotNull
040  private final Set<Path> queryAttributes;
041
042  private final boolean excluded;
043
044
045
046  /**
047   * Create a new SCIMResourceTrimmer.
048   *
049   * @param resourceType       The resource type definition for resources to
050   *                           trim.
051   * @param requestAttributes  The attributes in the request object or
052   *                           {@code null} for
053   *                           other requests.
054   * @param queryAttributes    The attributes from the 'attributes' or
055   *                           'excludedAttributes' query parameter.
056   * @param excluded           {@code true} if the queryAttributes came from
057   *                           the excludedAttributes query parameter.
058   */
059  public ScimResourceTrimmer(@NotNull final ResourceTypeDefinition resourceType,
060                             @NotNull final Set<Path> requestAttributes,
061                             @NotNull final Set<Path> queryAttributes,
062                             final boolean excluded)
063  {
064    this.resourceType      = resourceType;
065    this.requestAttributes = requestAttributes;
066    this.queryAttributes   = queryAttributes;
067    this.excluded          = excluded;
068  }
069
070
071
072  /**
073   * {@inheritDoc}
074   */
075  @Override
076  public boolean shouldReturn(@NotNull final Path path)
077  {
078    AttributeDefinition attributeDefinition =
079        resourceType.getAttributeDefinition(path);
080    AttributeDefinition.Returned returned = attributeDefinition == null ?
081        AttributeDefinition.Returned.DEFAULT :
082        attributeDefinition.getReturned();
083
084    switch(returned)
085    {
086      case ALWAYS:
087        return true;
088      case NEVER:
089        return false;
090      case REQUEST:
091        // Return only if it was one of the request attributes or if there are
092        // no request attributes, then only if it was one of the override query
093        // attributes.
094        return pathContains(requestAttributes, path) ||
095               (requestAttributes.isEmpty() && !excluded &&
096                pathContains(queryAttributes, path));
097      default:
098        // Return if it is not one of the excluded query attributes and no
099        // override query attributes are provided. If override query attributes
100        // are provided, only return if it is one of them.
101        if(excluded)
102        {
103          return !pathContains(queryAttributes, path);
104        }
105        else
106        {
107          return queryAttributes.isEmpty() ||
108                 pathContains(queryAttributes, path);
109        }
110    }
111  }
112
113  private boolean pathContains(@NotNull final Set<Path> paths,
114                               @NotNull final Path path)
115  {
116    // Exact path match
117    if (paths.contains(path))
118    {
119      return true;
120    }
121
122    if (!excluded)
123    {
124      // See if a sub-attribute of the given path is included in the list
125      // ie. include name if name.givenName is in the list.
126      for (Path p : paths)
127      {
128        if (p.size() > path.size() && path.equals(p.subPath(path.size())))
129        {
130          return true;
131        }
132      }
133    }
134
135    // See if the parent attribute of the given path is included in the list
136    // ie. include name.{anything} if name is in the list.
137    for (Path p = path; p.size() > 0; p = p.subPath(p.size() - 1))
138    {
139      if (paths.contains(p))
140      {
141        return true;
142      }
143    }
144
145    return false;
146  }
147}