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-2014 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>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)
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 }