com.unboundid.util.json.AuthenticationDetails Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of unboundid-ldapsdk Show documentation
Show all versions of unboundid-ldapsdk Show documentation
The UnboundID LDAP SDK for Java is a fast, comprehensive, and easy-to-use
Java API for communicating with LDAP directory servers and performing
related tasks like reading and writing LDIF, encoding and decoding data
using base64 and ASN.1 BER, and performing secure communication. This
package contains the Standard Edition of the LDAP SDK, which is a
complete, general-purpose library for communicating with LDAPv3 directory
servers.
/*
* Copyright 2015-2018 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright (C) 2015-2018 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.util.json;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import com.unboundid.ldap.sdk.BindRequest;
import com.unboundid.ldap.sdk.CRAMMD5BindRequest;
import com.unboundid.ldap.sdk.DIGESTMD5BindRequest;
import com.unboundid.ldap.sdk.DIGESTMD5BindRequestProperties;
import com.unboundid.ldap.sdk.EXTERNALBindRequest;
import com.unboundid.ldap.sdk.GSSAPIBindRequest;
import com.unboundid.ldap.sdk.GSSAPIBindRequestProperties;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.PLAINBindRequest;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SASLQualityOfProtection;
import com.unboundid.ldap.sdk.SimpleBindRequest;
import com.unboundid.util.NotMutable;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import static com.unboundid.util.json.JSONMessages.*;
/**
* This class provides a data structure and set of logic for interacting with
* the authentication details portion of a JSON object provided to the
* {@link LDAPConnectionDetailsJSONSpecification}.
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
final class AuthenticationDetails
implements Serializable
{
/**
* The name of the field that may be used to specify the authentication ID.
* This may be used in conjunction with the CRAM-MD5, DIGEST-MD5, GSSAPI, and
* PLAIN authentication types. It is required for use with those
* authentication types, and its value must be a string that represents an
* appropriate authentication ID for that authentication type.
*/
private static final String FIELD_AUTHENTICATION_ID = "authentication-id";
/**
* The name of the field that may be used to specify the type of
* authentication to perform. It is a required field, and its value must be a
* string with one of the following values: "none" (to indicate that no
* authentication should be performed), "simple" (to indicate LDAP simple
* authentication), "CRAM-MD5" (for the CRAM-MD5 SASL mechanism), "DIGEST-MD5"
* (for the DIGEST-MD5 SASL mechanism), "EXTERNAL" (for the EXTERNAL SASL
* mechanism), "GSSAPI" (for the GSSAPI SASL mechanism), or "PLAIN" (for the
* PLAIN SASL mechanism).
*/
private static final String FIELD_AUTHENTICATION_TYPE = "authentication-type";
/**
* The name of the field that may be used to specify the authorization ID.
* This may be used in conjunction with the DIGEST-MD5, EXTERNAL, GSSAPI, and
* PLAIN authentication types, and is an optional field for those
* authentication types. If present, its value must be a string that
* represents an appropriate authorization ID for that authentication type.
*/
private static final String FIELD_AUTHORIZATION_ID = "authorization-id";
/**
* The name of the field that may be used to specify the path to a JAAS
* configuration file. This is an optional field that may be used in
* conjunction with the GSSAPI authentication type. If present, its value
* must be a path to a valid JAAS configuration file. If it is not present,
* then a temporary configuration file will be automatically created and used
* for this purpose.
*/
private static final String FIELD_CONFIG_FILE_PATH = "config-file-path";
/**
* The name of the field that may be used to specify the bind DN. This field
* may be used in conjunction with the simple authentication type, and it is
* required for use in conjunction with that authentication type. Its value
* must be the DN to use when performing simple authentication, or an empty
* string to indicate anonymous authentication.
*/
private static final String FIELD_DN = "dn";
/**
* The name of the field that may be used to specify the address of the
* Kerberos KDC. This field may be used in conjunction with the GSSAPI
* authentication type, and it is an optional field for that authentication
* type. If present, its value must be a string that represents a resolvable
* name or IP address. If absent, the LDAP SDK will attempt to automatically
* determine the address of the Kerberos KDC from the underlying system.
*/
private static final String FIELD_KDC_ADDRESS = "kdc-address";
/**
* The name of the field that may be used to specify the password. This field
* may be used in conjunction with the simple, CRAM-MD5, DIGEST-MD5, GSSAPI,
* and PLAIN authentication types. It may not be used in conjunction with the
* password-file field. A password must be provided via either the password
* or password-file field for the simple CRAM-MD5, DIGEST-MD5, and PLAIN
* authentication types, and should be provided for the GSSAPI authentication
* type unless require-cached-credentials is true. If present, its value
* should be a string containing the password to use, or an empty string to
* indicate anonymous authentication.
*/
private static final String FIELD_PASSWORD = "password";
/**
* The name of the field that may be used to specify the path to a file
* containing the password. This field may be used in conjunction with the
* simple, CRAM-MD5, DIGEST-MD5, GSSAPI, and PLAIN authentication types. It
* may not be used in conjunction with the password field. A password must be
* provided via either the password or password-file field for the simple
* CRAM-MD5, DIGEST-MD5, and PLAIN authentication types, and should be
* provided for the GSSAPI authentication type unless
* require-cached-credentials is true. If present, its value should be a
* string containing the path to a file containing the password to use. The
* file should not be empty, so this field is not appropriate for anonymous
* authentication.
*/
private static final String FIELD_PASSWORD_FILE = "password-file";
/**
* The name of the field that may be used to specify the allowed quality of
* protection types. This may be used in conjunction with the DIGEST-MD5 and
* GSSAPI authentication types, and its value must be an array containing one
* or more of the following strings: "auth", "auth-int", and "auth-conf".
* If this is not provided, then a single-element array containing only the
* "auth" value will be used.
*/
private static final String FIELD_QOP = "qop";
/**
* The name of the field that may be used to specify the realm. This field
* may be used in conjunction with the DIGEST-MD5 and GSSAPI authentication
* types, and it is optional for those types. If present, its value must
* be a string that indicates the realm to use for the authentication. If
* it is absent, no realm will be provided to the server during the
* authentication process.
*/
private static final String FIELD_REALM = "realm";
/**
* The name of the field that may be used to indicate whether to attempt to
* automatically renew the Kerberos ticket-granting ticket. This field may be
* used in conjunction with the GSSAPI authentication type, and it is optional
* for use with that authentication type. If present, its value must be a
* boolean. If this field is absent, then a default value of {@code false}
* will be used.
*/
private static final String FIELD_RENEW_TGT = "renew-tgt";
/**
* The name of the field that may be used to indicate whether to require the
* use of cached credentials for authentication. This field may be used in
* conjunction with the GSSAPI authentication type, and it is optional for use
* with that authentication type. If present, its value must be a boolean.
* If this field is absent, then a default value of {@code false} will be
* used.
*/
private static final String FIELD_REQUIRE_CACHED_CREDENTIALS =
"require-cached-credentials";
/**
* The name of the field that may be used to specify the path to the Kerberos
* ticket cache to use if appropriate. This field may be used in conjunction
* with the GSSAPI authentication type, and it is optional for use with that
* authentication type. If present, its value must be the path to the
* Kerberos ticket cache. if this field is absent, then JVM will attempt to
* automatically determine the ticket cache path.
*/
private static final String FIELD_TICKET_CACHE_PATH = "ticket-cache-path";
/**
* The name of the field that may be used to indicate whether the client will
* be required to use credentials within the current subject. This field may
* be used in conjunction with the GSSAPI authentication type, and it is
* optional for use with that authentication type. If present, its value must
* be a boolean. If it is not provided, then a default value of {@code true}
* will be used.
*/
private static final String FIELD_USE_SUBJECT_CREDS_ONLY =
"use-subject-credentials-only";
/**
* The name of the field that may be used to indicate whether to use the
* Kerberos ticket cache. This field may be used in conjunction with the
* GSSAPI authentication type, and it is optional for use with that
* authentication type. If present, its value must be a boolean. If it is
* not provided, then a default value of {@code true} will be used.
*/
private static final String FIELD_USE_TICKET_CACHE = "use-ticket-cache";
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = 2798778432389082274L;
// The bind request created from the specification.
private final BindRequest bindRequest;
/**
* Creates a new set of authentication details from the information contained
* in the provided JSON object.
*
* @param connectionDetailsObject The JSON object containing the LDAP
* connection details specification.
*
* @throws LDAPException If there is a problem with the authentication
* details data in the provided JSON object.
*/
AuthenticationDetails(final JSONObject connectionDetailsObject)
throws LDAPException
{
final JSONObject o = LDAPConnectionDetailsJSONSpecification.getObject(
connectionDetailsObject,
LDAPConnectionDetailsJSONSpecification.FIELD_AUTHENTICATION_DETAILS);
if (o == null)
{
bindRequest = null;
return;
}
final String authType =
LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_AUTHENTICATION_TYPE, null);
final String loweAuthType = StaticUtils.toLowerCase(authType);
if (loweAuthType == null)
{
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_AUTH_DETAILS_MISSING_REQUIRED_FIELD.get(
LDAPConnectionDetailsJSONSpecification.
FIELD_AUTHENTICATION_DETAILS,
FIELD_AUTHENTICATION_TYPE));
}
else if (loweAuthType.equals("none"))
{
LDAPConnectionDetailsJSONSpecification.validateAllowedFields(o,
LDAPConnectionDetailsJSONSpecification.FIELD_AUTHENTICATION_DETAILS,
FIELD_AUTHENTICATION_TYPE);
bindRequest = null;
}
else if (loweAuthType.equals("simple"))
{
validateAllowedFields(o, authType,
FIELD_DN,
FIELD_PASSWORD,
FIELD_PASSWORD_FILE);
final String dn = LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_DN, null);
if (dn == null)
{
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_AUTH_DETAILS_MISSING_REQUIRED_FIELD_FOR_AUTH_TYPE.get(
FIELD_DN, authType));
}
final String password = getPassword(o, authType, false);
bindRequest = new SimpleBindRequest(dn, password);
}
else if (loweAuthType.equals("cram-md5") ||
loweAuthType.equals("crammd5"))
{
validateAllowedFields(o, authType,
FIELD_AUTHENTICATION_ID,
FIELD_PASSWORD,
FIELD_PASSWORD_FILE);
final String authID = LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_AUTHENTICATION_ID, null);
if (authID == null)
{
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_AUTH_DETAILS_MISSING_REQUIRED_FIELD_FOR_AUTH_TYPE.get(
FIELD_AUTHENTICATION_ID, authType));
}
final String password = getPassword(o, authType, false);
bindRequest = new CRAMMD5BindRequest(authID, password);
}
else if (loweAuthType.equals("digest-md5") ||
loweAuthType.equals("digestmd5"))
{
validateAllowedFields(o, authType,
FIELD_AUTHENTICATION_ID,
FIELD_AUTHORIZATION_ID,
FIELD_PASSWORD,
FIELD_PASSWORD_FILE,
FIELD_QOP,
FIELD_REALM);
final String authID = LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_AUTHENTICATION_ID, null);
if (authID == null)
{
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_AUTH_DETAILS_MISSING_REQUIRED_FIELD_FOR_AUTH_TYPE.get(
FIELD_AUTHENTICATION_ID, authType));
}
final String password = getPassword(o, authType, false);
final DIGESTMD5BindRequestProperties properties =
new DIGESTMD5BindRequestProperties(authID, password);
properties.setAuthorizationID(
LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_AUTHORIZATION_ID, null));
properties.setRealm(LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_REALM, null));
properties.setAllowedQoP(getAllowedQoP(o));
bindRequest = new DIGESTMD5BindRequest(properties);
}
else if (loweAuthType.equals("external"))
{
validateAllowedFields(o, authType,
FIELD_AUTHORIZATION_ID);
final String authzID = LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_AUTHORIZATION_ID, null);
bindRequest = new EXTERNALBindRequest(authzID);
}
else if (loweAuthType.equals("gssapi") ||
loweAuthType.equals("gss-api"))
{
validateAllowedFields(o, authType,
FIELD_AUTHENTICATION_ID,
FIELD_AUTHORIZATION_ID,
FIELD_PASSWORD,
FIELD_PASSWORD_FILE,
FIELD_CONFIG_FILE_PATH,
FIELD_KDC_ADDRESS,
FIELD_QOP,
FIELD_REALM,
FIELD_RENEW_TGT,
FIELD_REQUIRE_CACHED_CREDENTIALS,
FIELD_TICKET_CACHE_PATH,
FIELD_USE_SUBJECT_CREDS_ONLY,
FIELD_USE_TICKET_CACHE);
final String authID = LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_AUTHENTICATION_ID, null);
if (authID == null)
{
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_AUTH_DETAILS_MISSING_REQUIRED_FIELD_FOR_AUTH_TYPE.get(
FIELD_AUTHENTICATION_ID, authType));
}
final String password = getPassword(o, authType, true);
final GSSAPIBindRequestProperties properties =
new GSSAPIBindRequestProperties(authID, password);
properties.setAuthorizationID(
LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_AUTHORIZATION_ID, null));
properties.setRealm(LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_REALM, null));
properties.setAllowedQoP(getAllowedQoP(o));
properties.setConfigFilePath(
LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_CONFIG_FILE_PATH, null));
properties.setKDCAddress(
LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_KDC_ADDRESS, null));
properties.setRenewTGT(
LDAPConnectionDetailsJSONSpecification.getBoolean(o,
FIELD_RENEW_TGT, false));
properties.setRequireCachedCredentials(
LDAPConnectionDetailsJSONSpecification.getBoolean(o,
FIELD_REQUIRE_CACHED_CREDENTIALS, false));
properties.setTicketCachePath(
LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_TICKET_CACHE_PATH, null));
properties.setUseSubjectCredentialsOnly(
LDAPConnectionDetailsJSONSpecification.getBoolean(o,
FIELD_USE_SUBJECT_CREDS_ONLY, true));
properties.setUseTicketCache(
LDAPConnectionDetailsJSONSpecification.getBoolean(o,
FIELD_USE_TICKET_CACHE, true));
if ((password == null) && (! properties.requireCachedCredentials()))
{
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_AUTH_DETAILS_MISSING_GSSAPI_PASSWORD.get(FIELD_PASSWORD,
FIELD_PASSWORD_FILE, authType,
FIELD_REQUIRE_CACHED_CREDENTIALS));
}
bindRequest = new GSSAPIBindRequest(properties);
}
else if (loweAuthType.equals("plain"))
{
validateAllowedFields(o, authType,
FIELD_AUTHENTICATION_ID,
FIELD_AUTHORIZATION_ID,
FIELD_PASSWORD,
FIELD_PASSWORD_FILE);
final String authID = LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_AUTHENTICATION_ID, null);
if (authID == null)
{
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_AUTH_DETAILS_MISSING_REQUIRED_FIELD_FOR_AUTH_TYPE.get(
FIELD_AUTHENTICATION_ID, authType));
}
final String authzID = LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_AUTHORIZATION_ID, null);
final String password = getPassword(o, authType, false);
bindRequest = new PLAINBindRequest(authID, authzID, password);
}
else
{
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_AUTH_DETAILS_UNRECOGNIZED_TYPE.get(authType));
}
}
/**
* Retrieves the bind request created from the authentication details.
*
* @return The bind request created from the authentication details.
*/
BindRequest getBindRequest()
{
return bindRequest;
}
/**
* Validates that all of the provided fields are allowed for use in
* conjunction with the specified authentication type.
*
* @param o The JSON object to process.
* @param authType The name of the authentication type.
* @param allowedFields The names of the fields allowed for use with the
* specified authentication type.
*
* @throws LDAPException If the provided object contains any field not
* permitted in conjunction with the specified
* authentication type.
*/
private static void validateAllowedFields(final JSONObject o,
final String authType,
final String... allowedFields)
throws LDAPException
{
final HashSet s = new HashSet<>(Arrays.asList(allowedFields));
for (final String fieldName : o.getFields().keySet())
{
if (fieldName.equals(FIELD_AUTHENTICATION_TYPE))
{
continue;
}
if (! s.contains(fieldName))
{
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_AUTH_DETAILS_FIELD_NOT_PERMITTED_FOR_AUTH_TYPE.get(fieldName,
authType));
}
}
}
/**
* Retrieves a password from the provided JSON object. The password must
* either be directly contained in the object's password field, or it must be
* in a file referenced by the object's password-file object.
*
* @param o The JSON object to process.
* @param authType The authentication type.
* @param optional Indicates whether the password should be optional for the
* bind request.
*
* @return The password, or {@code null} if no password was provided and the
* password is optional.
*
* @throws LDAPException If no password is available or a problem is
* encountered while trying to read the password from
* a file.
*/
private static String getPassword(final JSONObject o, final String authType,
final boolean optional)
throws LDAPException
{
final String password = LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_PASSWORD, null);
if (password != null)
{
LDAPConnectionDetailsJSONSpecification.rejectConflictingFields(o,
FIELD_PASSWORD, FIELD_PASSWORD_FILE);
return password;
}
final String path = LDAPConnectionDetailsJSONSpecification.getString(o,
FIELD_PASSWORD_FILE, null);
if (path == null)
{
if (optional)
{
return null;
}
else
{
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_AUTH_DETAILS_NO_PASSWORD.get(FIELD_PASSWORD,
FIELD_PASSWORD_FILE, authType));
}
}
return LDAPConnectionDetailsJSONSpecification.getStringFromFile(path,
FIELD_PASSWORD_FILE);
}
/**
* Retrieves the allowed quality of protection values from the provided JSON
* object.
*
* @param o The JSON object to process.
*
* @return A set containing the allowed quality of protection values.
*
* @throws LDAPException If no password is available or a problem is
* encountered while trying to read the password from
* a file.
*/
private static List getAllowedQoP(final JSONObject o)
throws LDAPException
{
final JSONValue v = o.getField(FIELD_QOP);
if (v == null)
{
return Collections.singletonList(SASLQualityOfProtection.AUTH);
}
if (v instanceof JSONString)
{
return SASLQualityOfProtection.decodeQoPList(
((JSONString) v).stringValue());
}
else if (v instanceof JSONArray)
{
final JSONArray a = (JSONArray) v;
final ArrayList qopList =
new ArrayList<>(a.size());
for (final JSONValue av : a.getValues())
{
if (av instanceof JSONString)
{
final SASLQualityOfProtection qop = SASLQualityOfProtection.forName(
((JSONString) av).stringValue());
if (qop == null)
{
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_AUTH_DETAILS_INVALID_QOP.get(FIELD_QOP));
}
else
{
qopList.add(qop);
}
}
else
{
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_AUTH_DETAILS_INVALID_QOP.get(FIELD_QOP));
}
}
return qopList;
}
else
{
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_AUTH_DETAILS_INVALID_QOP.get(FIELD_QOP));
}
}
}