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.api;
028    
029    
030    import com.unboundid.directory.sdk.http.config.OAuthTokenHandlerConfig;
031    import com.unboundid.directory.sdk.common.internal.ExampleUsageProvider;
032    import com.unboundid.directory.sdk.common.internal.Reconfigurable;
033    import com.unboundid.directory.sdk.common.internal.UnboundIDExtension;
034    import com.unboundid.directory.sdk.ds.internal.DirectoryServerExtension;
035    import com.unboundid.directory.sdk.http.types.HTTPServerContext;
036    import com.unboundid.directory.sdk.proxy.internal.DirectoryProxyServerExtension;
037    import com.unboundid.ldap.sdk.DN;
038    import com.unboundid.ldap.sdk.LDAPException;
039    import com.unboundid.ldap.sdk.ResultCode;
040    import com.unboundid.scim.sdk.OAuthToken;
041    import com.unboundid.scim.sdk.SCIMRequest;
042    import com.unboundid.scim.sdk.OAuthTokenStatus;
043    import com.unboundid.util.Extensible;
044    import com.unboundid.util.ThreadSafety;
045    import com.unboundid.util.ThreadSafetyLevel;
046    import com.unboundid.util.args.ArgumentException;
047    import com.unboundid.util.args.ArgumentParser;
048    
049    import java.security.GeneralSecurityException;
050    import java.util.Collections;
051    import java.util.List;
052    import 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>draft-ietf-oauth-v2-bearer-23</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>draft-ietf-oauth-v2-bearer-23</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>draft-ietf-oauth-v2-threatmodel-07</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)
135    public abstract class OAuthTokenHandler
136           implements UnboundIDExtension, Reconfigurable<OAuthTokenHandlerConfig>,
137                      ExampleUsageProvider
138    {
139      /**
140       * Creates a new instance of this alert 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    }