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