001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * docs/licenses/cddl.txt
011 * or http://www.opensource.org/licenses/cddl1.php.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * docs/licenses/cddl.txt.  If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 *      Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 *      Portions Copyright 2010-2024 Ping Identity Corporation
026 */
027package com.unboundid.directory.sdk.http.api;
028
029
030import com.unboundid.directory.sdk.http.config.OAuthTokenHandlerConfig;
031import com.unboundid.directory.sdk.common.internal.ExampleUsageProvider;
032import com.unboundid.directory.sdk.common.internal.Reconfigurable;
033import com.unboundid.directory.sdk.common.internal.UnboundIDExtension;
034import com.unboundid.directory.sdk.ds.internal.DirectoryServerExtension;
035import com.unboundid.directory.sdk.http.types.HTTPServerContext;
036import com.unboundid.directory.sdk.proxy.internal.DirectoryProxyServerExtension;
037import com.unboundid.ldap.sdk.DN;
038import com.unboundid.ldap.sdk.LDAPException;
039import com.unboundid.ldap.sdk.ResultCode;
040import com.unboundid.scim.sdk.OAuthToken;
041import com.unboundid.scim.sdk.SCIMRequest;
042import com.unboundid.scim.sdk.OAuthTokenStatus;
043import com.unboundid.util.Extensible;
044import com.unboundid.util.ThreadSafety;
045import com.unboundid.util.ThreadSafetyLevel;
046import com.unboundid.util.args.ArgumentException;
047import com.unboundid.util.args.ArgumentParser;
048
049import java.security.GeneralSecurityException;
050import java.util.Collections;
051import java.util.List;
052import java.util.Map;
053
054
055/**
056 * This class defines an API that must be implemented by extensions which will
057 * handle incoming SCIM requests with OAuth 2.0 bearer token authentication.
058 * The OAuthTokenHandler is responsible for decoding the bearer token and
059 * checking it for authenticity and validity.
060 * <BR><BR>
061 * OAuth provides a method for clients to access a protected resource on
062 * behalf of a resource owner. In the general case, before a client can
063 * access a protected resource, it must first obtain an authorization
064 * grant from the resource owner and then exchange the authorization
065 * grant for an access token. The access token represents the grant's
066 * scope, duration, and other attributes specified by the authorization
067 * grant. The client accesses the protected resource by presenting the
068 * access token to the resource server (i.e. the Directory or Proxy Server with
069 * the SCIM HTTP Servlet enabled).
070 * <BR><BR>
071 * The access token provides an abstraction, replacing different
072 * authorization constructs (e.g., username and password, assertion) for
073 * a single token understood by the resource server. This abstraction
074 * enables issuing access tokens valid for a short time period, as well
075 * as removing the resource server's need to understand a wide range of
076 * authentication schemes. See "OAuth 2.0 Authorization Framework: Bearer
077 * Token Usage" (<i>RFC 6750</i>) for the full
078 * specification and details.
079 * <BR><BR>
080 * TLS security is required to use OAuth 2.0 bearer tokens, as specified in
081 * <i>RFC 6750</i>. A bearer token may be used by any party
082 * in possession of that token (the "bearer"), and thus needs to be protected
083 * when transmitted across the network. Implementations of this API should take
084 * special care to verify that the token came from a trusted source (using a
085 * secret key or some other signing mechanism to prove that the token is
086 * authentic). Please read "OAuth 2.0 Threat Model and Security Considerations"
087 * (<i>RFC 6819</i>) for a comprehensive list of
088 * security threats to consider when working with OAuth bearer tokens.
089 * <BR><BR>
090 * The OAuthTokenHandler is also responsible for extracting an authorization DN
091 * from the bearer token (or otherwise providing one), which will be used to
092 * apply access controls before returning the protected resource. There are also
093 * methods to extract the expiration date of the token as well as verify that
094 * the intended audience is this server (to deal with token redirect).
095 * <BR><BR>
096 * The order these methods are called by the SCIM HTTP Servlet Extension is as
097 * follows:
098 * <ol>
099 *   <li><i>decodeOAuthToken()</i></li>
100 *   <li><i>isTokenAuthentic()</i></li>
101 *   <li><i>isTokenForThisServer()</i></li>
102 *   <li><i>isTokenExpired()</i></li>
103 *   <li><i>validateToken()</i></li>
104 *   <li><i>getAuthzDN()</i></li>
105 * </ol>
106 * If any of the methods fail or return an error result, the server will return
107 * an appropriate "unauthorized" response to the client.
108 * <BR>
109 * <H2>Configuring OAuth Token Handlers</H2>
110 * In order to configure a token handler created using this API, use a command
111 * like:
112 * <PRE>
113 *      dsconfig create-oauth-token-handler \
114 *           --handler-name "<I>{handler-name}</I>" \
115 *           --type third-party \
116 *           --set "extension-class:<I>{class-name}</I>" \
117 *           --set "extension-argument:<I>{name=value}</I>"
118 * </PRE>
119 * where "<I>{handler-name}</I>" is the name to use for the token handler
120 * instance, "<I>{class-name}</I>" is the fully-qualified name of the Java class
121 * that extends {@code com.unboundid.directory.sdk.http.api.OAuthTokenHandler},
122 * and "<I>{name=value}</I>" represents name-value pairs for any arguments to
123 * provide to the token handler.  If multiple arguments should be provided to
124 * the token handler, then the
125 * "<CODE>--set extension-argument:<I>{name=value}</I></CODE>" option should be
126 * provided multiple times.
127 *
128 * @see  com.unboundid.directory.sdk.http.scripting.ScriptedOAuthTokenHandler
129 */
130@Extensible()
131@DirectoryServerExtension()
132@DirectoryProxyServerExtension(appliesToLocalContent=false,
133     appliesToRemoteContent=true)
134@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE)
135public abstract class OAuthTokenHandler
136       implements UnboundIDExtension, Reconfigurable<OAuthTokenHandlerConfig>,
137                  ExampleUsageProvider
138{
139  /**
140   * Creates a new instance of this token handler.  All token handler
141   * implementations must include a default constructor, but any initialization
142   * should generally be done in the {@code initializeTokenHandler} method.
143   */
144  public OAuthTokenHandler()
145  {
146    // No implementation is required.
147  }
148
149
150
151  /**
152   * {@inheritDoc}
153   */
154  @Override
155  public abstract String getExtensionName();
156
157
158
159  /**
160   * {@inheritDoc}
161   */
162  @Override
163  public abstract String[] getExtensionDescription();
164
165
166
167  /**
168   * {@inheritDoc}
169   */
170  @Override
171  public void defineConfigArguments(final ArgumentParser parser)
172         throws ArgumentException
173  {
174    // No arguments will be allowed by default.
175  }
176
177
178
179  /**
180   * Initializes this token handler.
181   *
182   * @param  serverContext  A handle to the server context for the server in
183   *                        which this extension is running.
184   * @param  config         The general configuration for this token handler.
185   * @param  parser         The argument parser which has been initialized from
186   *                        the configuration for this token handler.
187   *
188   * @throws  LDAPException  If a problem occurs while initializing this token
189   *                         handler.
190   */
191  public void initializeTokenHandler(final HTTPServerContext serverContext,
192                                     final OAuthTokenHandlerConfig config,
193                                     final ArgumentParser parser)
194         throws LDAPException
195  {
196    // No initialization will be performed by default.
197  }
198
199
200
201  /**
202   * {@inheritDoc}
203   */
204  @Override
205  public boolean isConfigurationAcceptable(final OAuthTokenHandlerConfig config,
206                      final ArgumentParser parser,
207                      final List<String> unacceptableReasons)
208  {
209    // No extended validation will be performed by default.
210    return true;
211  }
212
213
214
215  /**
216   * {@inheritDoc}
217   */
218  @Override
219  public ResultCode applyConfiguration(final OAuthTokenHandlerConfig config,
220                                       final ArgumentParser parser,
221                                       final List<String> adminActionsRequired,
222                                       final List<String> messages)
223  {
224    // By default, no configuration changes will be applied.  If there are any
225    // arguments, then add an admin action message indicating that the extension
226    // needs to be restarted for any changes to take effect.
227    if (! parser.getNamedArguments().isEmpty())
228    {
229      adminActionsRequired.add(
230        "No configuration change has actually been applied. The new " +
231        "configuration will not take effect until the HTTP Connection " +
232        "Handler is disabled and re-enabled or until the server is restarted.");
233    }
234
235    return ResultCode.SUCCESS;
236  }
237
238
239
240  /**
241   * Performs any cleanup which may be necessary when this token handler is to
242   * be taken out of service.
243   */
244  public void finalizeTokenHandler()
245  {
246    // No implementation is required.
247  }
248
249
250
251  /**
252   * {@inheritDoc}
253   */
254  @Override
255  public Map<List<String>,String> getExamplesArgumentSets()
256  {
257    return Collections.emptyMap();
258  }
259
260
261
262  /**
263   * Creates an {@link OAuthToken} instance from the incoming token value.
264   * <p>
265   * Implementers may choose to return a subclass of {@link OAuthToken} in order
266   * to provide convenience methods for interacting with the token. This can be
267   * helpful because the returned {@link OAuthToken} is passed to all of the
268   * other methods in this class.
269   *
270   * @param base64TokenValue the base64-encoded bearer token value
271   * @return a {@link OAuthToken} instance. This must not be {@code null}.
272   * @throws GeneralSecurityException if there is an error decoding the token
273   */
274  public abstract OAuthToken decodeOAuthToken(final String base64TokenValue)
275          throws GeneralSecurityException;
276
277
278
279  /**
280   * Determines whether the given token is expired.
281   *
282   * @param token the OAuth 2.0 bearer token.
283   * @return {@code true} if the token is already expired, {@code false} if not.
284   * @throws GeneralSecurityException if there is an error determining the
285   *         token's expiration date
286   */
287  public abstract boolean isTokenExpired(final OAuthToken token)
288          throws GeneralSecurityException;
289
290
291
292  /**
293   * Determines whether the incoming token is authentic (i.e. that it came from
294   * a trusted authorization server and not an attacker). Implementers are
295   * encouraged to use signed tokens and use this method to verify the
296   * signature, but other methods such as symmetric key encryption (using a
297   * shared secret) can be used as well.
298   *
299   * @param token the OAuth 2.0 bearer token.
300   * @return {@code true} if the bearer token can be verified as authentic and
301   *         originating from a trusted source, {@code false} if not.
302   * @throws GeneralSecurityException if there is an error determining whether
303   *         the token is authentic
304   */
305  public abstract boolean isTokenAuthentic(final OAuthToken token)
306          throws GeneralSecurityException;
307
308
309
310  /**
311   * Determines whether the incoming token is targeted for this server. This
312   * allows the implementation to reject the token early in the validation
313   * process if it can see that the intended recipient was not this server.
314   *
315   * @param token the OAuth 2.0 bearer token.
316   * @return {@code true} if the bearer token identifies this server as the
317   *         intended recipient, {@code false} if not.
318   * @throws GeneralSecurityException if there is an error determining whether
319   *         the token is for this server
320   */
321  public abstract boolean isTokenForThisServer(final OAuthToken token)
322          throws GeneralSecurityException;
323
324
325
326  /**
327   * Determines whether the incoming token is valid for the given request. This
328   * method should verify that the token is legitimate and grants access to the
329   * requested resource specified in the {@link SCIMRequest}. This typically
330   * involves checking the token scope and any other attributes granted by the
331   * authorization grant. Implementations may need to call back to the
332   * authorization server to verify that the token is still valid and has not
333   * been revoked.
334   *
335   * @param token the OAuth 2.0 bearer token.
336   * @param scimRequest the {@link SCIMRequest} that we are validating.
337   * @return an {@link OAuthTokenStatus} object which indicates whether the
338   *         bearer token is valid and grants access to the target resource.
339   *         This must not be {@code null}.
340   * @throws GeneralSecurityException if there is an error determining whether
341   *         the token is valid
342   */
343  public abstract OAuthTokenStatus validateToken(final OAuthToken token,
344                                                 final SCIMRequest scimRequest)
345          throws GeneralSecurityException;
346
347
348
349  /**
350   * Extracts the DN of the authorization entry (for which to apply access
351   * controls) from the incoming token.
352   * <p>
353   * This may require performing an LDAP search in order to find the DN that
354   * matches a certain attribute value contained in the token.
355   *
356   * @param token the OAuth 2.0 bearer token.
357   * @return the authorization DN to use.
358   * @throws GeneralSecurityException if there is an error determining the
359   *         authorization user DN
360   */
361  public abstract DN getAuthzDN(final OAuthToken token)
362          throws GeneralSecurityException;
363}