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     *      Copyright 2010-2013 UnboundID Corp.
026     */
027    package com.unboundid.directory.sdk.http.scripting;
028    
029    
030    import com.unboundid.directory.sdk.common.internal.Reconfigurable;
031    import com.unboundid.directory.sdk.ds.internal.DirectoryServerExtension;
032    import com.unboundid.directory.sdk.http.config.OAuthTokenHandlerConfig;
033    import com.unboundid.directory.sdk.http.types.HTTPServerContext;
034    import com.unboundid.directory.sdk.proxy.internal.DirectoryProxyServerExtension;
035    import com.unboundid.ldap.sdk.DN;
036    import com.unboundid.ldap.sdk.LDAPException;
037    import com.unboundid.ldap.sdk.ResultCode;
038    import com.unboundid.scim.sdk.OAuthToken;
039    import com.unboundid.scim.sdk.OAuthTokenStatus;
040    import com.unboundid.scim.sdk.SCIMRequest;
041    import com.unboundid.util.Extensible;
042    import com.unboundid.util.ThreadSafety;
043    import com.unboundid.util.ThreadSafetyLevel;
044    import com.unboundid.util.args.ArgumentException;
045    import com.unboundid.util.args.ArgumentParser;
046    
047    import java.security.GeneralSecurityException;
048    import 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>draft-ietf-oauth-v2-bearer-23</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>draft-ietf-oauth-v2-bearer-23</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>draft-ietf-oauth-v2-threatmodel-07</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)
132    public 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    }