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