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}