001/*
002 * Copyright 2015-2021 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.providers;
019
020import com.unboundid.scim2.common.exceptions.NotImplementedException;
021import com.unboundid.scim2.common.exceptions.ScimException;
022import com.unboundid.scim2.common.utils.ApiConstants;
023import com.unboundid.scim2.server.utils.ServerUtils;
024
025import javax.annotation.Priority;
026import javax.ws.rs.Priorities;
027import javax.ws.rs.container.ContainerRequestContext;
028import javax.ws.rs.container.ContainerRequestFilter;
029import javax.ws.rs.container.PreMatching;
030import javax.ws.rs.core.MultivaluedMap;
031import javax.ws.rs.core.Response;
032import javax.ws.rs.core.SecurityContext;
033import javax.ws.rs.core.UriBuilder;
034import javax.ws.rs.ext.Provider;
035import java.io.IOException;
036import java.util.ArrayList;
037import java.util.Collection;
038import java.util.Collections;
039
040/**
041 * A ContainerRequestFilter implementation to resolve the /Me alias to the
042 * path of the resource that represents the authenticated subject. This
043 * implementation will use the user principal within the SecurityContext
044 * as the resource ID and assumes the resource is part of the /Users resource
045 * type.
046 */
047@Provider
048@PreMatching
049@Priority(Priorities.HEADER_DECORATOR)
050public class AuthenticatedSubjectAliasFilter implements ContainerRequestFilter
051{
052  /**
053   * {@inheritDoc}
054   */
055  public void filter(final ContainerRequestContext requestContext)
056      throws IOException
057  {
058    String requestPath = requestContext.getUriInfo().getPath();
059    for(String alias : getAliases())
060    {
061      if(requestPath.startsWith(alias + "/") || requestPath.equals(alias))
062      {
063        String authSubjectPath;
064        try
065        {
066          authSubjectPath = ServerUtils.encodeTemplateNames(
067              getAuthenticatedSubjectPath(
068                  requestContext.getSecurityContext()));
069          UriBuilder newRequestUri =
070              requestContext.getUriInfo().getBaseUriBuilder();
071          newRequestUri.path(authSubjectPath +
072              requestPath.substring(alias.length()));
073          MultivaluedMap<String, String> queryParams =
074              requestContext.getUriInfo().getQueryParameters();
075          for (String key : queryParams.keySet())
076          {
077            String escapedKey = ServerUtils.encodeTemplateNames(key);
078            ArrayList<String> escapedValues = new ArrayList<>();
079            for (String value : queryParams.get(key))
080            {
081              escapedValues.add(ServerUtils.encodeTemplateNames(value));
082            }
083            newRequestUri.queryParam(escapedKey, escapedValues.toArray());
084          }
085
086          requestContext.setRequestUri(newRequestUri.build());
087        }
088        catch (ScimException e)
089        {
090          requestContext.abortWith(
091              ServerUtils.setAcceptableType(Response.
092                  status(e.getScimError().getStatus()).
093                  entity(e.getScimError()),
094                  requestContext.getAcceptableMediaTypes()).build());
095        }
096        break;
097      }
098    }
099  }
100
101  /**
102   * Get the path of the resource the represents the authenticated subject.
103   *
104   * @param securityContext The request's security context.
105   * @return The path relative to the base URI.
106   * @throws ScimException if an error occurs while resolving the path.
107   */
108  protected String getAuthenticatedSubjectPath(
109      final SecurityContext securityContext)
110      throws ScimException
111  {
112    if(securityContext == null || securityContext.getUserPrincipal() == null)
113    {
114      throw new NotImplementedException("/Me not supported");
115    }
116
117    return "Users/"+ securityContext.getUserPrincipal().toString();
118  }
119
120  /**
121   * Get the aliases for the authenticated subject.
122   *
123   * @return The aliases for the authenticated subject.
124   */
125  protected Collection<String> getAliases()
126  {
127    return Collections.singleton(ApiConstants.ME_ENDPOINT);
128  }
129}