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-2023 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}