com.vmware.identity.idm.ValidateUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.identity.idm.ValidateUtil.java

Source

/*
 *
 *  Copyright (c) 2012-2015 VMware, Inc.  All Rights Reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not
 *  use this file except in compliance with the License.  You may obtain a copy
 *  of the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, without
 *  warranties or conditions of any kind, EITHER EXPRESS OR IMPLIED.  See the
 *  License for the specific language governing permissions and limitations
 *  under the License.
 *
 */
package com.vmware.identity.idm;

import java.lang.reflect.Array;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

import com.vmware.identity.diagnostics.DiagnosticsLoggerFactory;
import com.vmware.identity.diagnostics.IDiagnosticsLogger;

/**
 * Provides validate methods for checking the value of given argument. If
 * validation passes the methods will return silently, otherwise -
 * {@link IllegalArgumentException} should be thrown.
 */
public final class ValidateUtil {

    public final static char UPN_SEPARATOR = '@';
    public final static char NETBIOS_SEPARATOR = '\\';
    public final static String spn_prefix = "STS/";

    /**
     * Validate the the given value is a positive number.
     *
     * @param fieldValue
     *           field value to validate
     * @param fieldName
     *           field name
     *
     * @throws IllegalArgumentException
     *            on validation failure
     */
    public static void validatePositiveNumber(long fieldValue, String fieldName) {

        if (fieldValue <= 0) {
            logAndThrow(String.format("%s should be a positive number: %d", fieldName, fieldValue));
        }
    }

    /**
     * Validate the the given value is a non-negative number.
     *
     * @param fieldValue
     *           field value to validate
     * @param fieldName
     *           field name
     *
     * @throws IllegalArgumentException
     *            on validation failure
     */
    public static void validateNonNegativeNumber(long fieldValue, String fieldName) {

        if (fieldValue < 0) {
            logAndThrow(String.format("%s should be a non-negative number: %d", fieldName, fieldValue));
        }
    }

    /**
     * Same as {@link #validateNotNull(Object, String)} but just for {@code
     * null} value
     *
     * @throws IllegalArgumentException
     *            on validation failure
     */
    public static void validateNotNull(Object fieldValue, String fieldName) {

        if (fieldValue == null) {
            logAndThrow(String.format("'%s' value should not be NULL", fieldName));
        }
    }

    /**
     * Validates that given value is <code>null</code>.
     *
     * @throws IllegalArgumentException
     *            on validation failure
     */
    public static void validateNull(Object fieldValue, String fieldName) {

        if (fieldValue != null) {
            logAndThrow(String.format("'%s' value should be NULL", fieldName));
        }
    }

    /**
     * Validates that given certificate is <code>valid</code>.
     * clockTolerance - value of current clock tolerance in milliseconds
     * @throws IllegalArgumentException
     *            on validation failure
     */
    public static void validateSolutionDetail(SolutionDetail fieldValue, String fieldName, long clockTolerance) {

        X509Certificate cert = fieldValue.getCertificate();
        ValidateUtil.validateNotNull(cert, "Solution user certificate");
        try {
            cert.checkValidity();
        } catch (CertificateException ex) {
            if (ex instanceof CertificateNotYetValidException) {
                // Check to see whether certificate is within clock tolerance
                // if so do not throw, cert passes the validation
                if (cert.getNotBefore().getTime() <= System.currentTimeMillis() + clockTolerance) {
                    return;
                }
            }

            if (ex instanceof CertificateExpiredException) {
                // Check to see whether certificate is within clock tolerance
                // if so do not throw, cert passes the validation
                if (cert.getNotAfter().getTime() >= System.currentTimeMillis() - clockTolerance) {
                    return;
                }
            }

            logAndThrow(String.format("'%s' certificate is invalid - " + "certificateException %s", fieldName,
                    ex.toString()));
        }
    }

    /**
     * Validates that given certificate type is <code>valid</code>.
     * @throws IllegalArgumentException
     *            on validation failure
     */
    public static void validateCertType(CertificateType fieldValue, String fieldName) {
        if (fieldValue != CertificateType.LDAP_TRUSTED_CERT && fieldValue != CertificateType.STS_TRUST_CERT) {
            logAndThrow(String.format("'%s' value is invalid certificate type", fieldName));
        }
    }

    /**
     * Checks validity of the given certificate.
     * @throws IllegalArgumentException
     *            on validation failure
     */
    public static void validateCertificate(X509Certificate cert) {
        try {
            cert.checkValidity();
        } catch (Exception e) {
            logAndThrow(String.format("Certificate is not valid: %s", e.getMessage()));
        }
    }

    /**
     * Check whether given object value is empty. Depending on argument runtime
     * type <i>empty</i> means:
     * <ul>
     * <li>for java.lang.String type - {@code null} value or empty string</li>
     * <li>for array type - {@code null} value or zero length array</li>
     * <li>for any other type - {@code null} value</li>
     * </ul>
     * <p>
     * Note that java.lang.String values should be handled by
     * {@link #isEmpty(String)}
     * </p>
     *
     * @param obj
     *           any object or {@code null}
     *
     * @return {@code true} when the object value is {@code null} or empty array
     */
    public static boolean isEmpty(Object obj) {

        if (obj == null) {
            return true;
        }

        if (obj.getClass().equals(String.class)) {
            return ((String) obj).isEmpty();
        }

        if (obj.getClass().isArray()) {
            return Array.getLength(obj) == 0;
        }

        if (obj instanceof java.util.Collection<?>) {
            return ((java.util.Collection<?>) obj).isEmpty();
        }

        final String message = "String, java.lang.Array or java.util.Collection " + "expected but "
                + obj.getClass().getName() + " was found ";

        getLog().error(message);
        throw new IllegalArgumentException(message);
    }

    /**
     * Validates that given value is not empty (as defined by
     * {@link #isEmpty(Object)} contract). If validation check fails -
     * {@link IllegalArgumentException} will be thrown.
     * <p>
     * Useful for validation of required input arguments passed by end user at
     * public methods, etc.
     *
     * @param fieldValue
     *           field value to validate
     * @param fieldName
     *           field name
     *
     * @throws IllegalArgumentException
     *            on validation failure
     */
    public static void validateNotEmpty(Object fieldValue, String fieldName) {

        if (isEmpty(fieldValue)) {
            logAndThrow(String.format("'%s' value should not be empty", fieldName));
        }
    }

    public static void validateIdsDomainName(String domainName, DomainType domainType) {
        validateNotEmpty(domainName, "ids domainName");
    }

    /**
     * Validate IdentityStoreData attributes. Includes:
     *  domainName format validation
     *  domainAliase format validation
     *
     *
     * @param idsData
     */
    public static void validateIdsDomainNameNAlias(IIdentityStoreData idsData) {
        if (idsData.getDomainType().equals(DomainType.LOCAL_OS_DOMAIN))
            return;

        validateIdsDomainName(idsData.getName(), idsData.getDomainType());

        if (null != idsData.getExtendedIdentityStoreData()) {
            String domainAlias = idsData.getExtendedIdentityStoreData().getAlias();
            if (domainAlias != null) {
                validateNotEmpty(domainAlias, "ids domainAlias");
            }
        }
    }

    public static String normalizeIdsKrbUserName(String krbUpn) {
        char[] validUpnSeparator = { UPN_SEPARATOR };
        int idxSep = getValidIdxAccountNameSeparator(krbUpn, validUpnSeparator);
        // if no '@' or multiple '@' or leading '@' exception is thrown
        if (idxSep == -1 || idxSep == 0) {
            logAndThrow(String.format("userkrbPrincipal [%s] is not in valid UPN format ", krbUpn));
        }
        String userName = krbUpn.substring(0, idxSep);
        validateNotEmpty(userName, "user account name in UPN");
        String domain = krbUpn.substring(idxSep + 1);
        validateNotEmpty(domain, "user REALM in UPN");

        return String.format("%s%c%s", userName, UPN_SEPARATOR, domain);
    }

    public static void validateUpn(String upn, String fieldName) {
        char[] validUpnSeparator = { UPN_SEPARATOR };
        int idxSep = getValidIdxAccountNameSeparator(upn, validUpnSeparator);
        // if no '@' or multiple '@' or leading '@' exception is thrown
        if (idxSep == -1 || idxSep == 0) {
            logAndThrow(String.format("%s=[%s] is invalid: not a valid UPN format ", fieldName, upn));
        }
        String userName = upn.substring(0, idxSep);
        validateNotEmpty(userName, "%s=[%s] is invalid: user name in UPN cannot be empty.");
        String domain = upn.substring(idxSep + 1);
        validateNotEmpty(domain, "%s=[%s] is invalid: domain suffix in UPN cannot be empty.");
    }

    public static void validateNetBios(String netbiosName, String fieldName) {
        char[] validUpnSeparator = { NETBIOS_SEPARATOR };
        int idxSep = getValidIdxAccountNameSeparator(netbiosName, validUpnSeparator);
        // if no '\\' or multiple '\\' or leading '\\' exception is thrown
        if (idxSep == -1 || idxSep == 0) {
            logAndThrow(
                    String.format("%s=[%s] is invalid: not a valid netbios name format ", fieldName, netbiosName));
        }
        String userName = netbiosName.substring(0, idxSep);
        validateNotEmpty(userName, "%s=[%s] is invalid: user name in netbios name cannot be empty.");
        String domain = netbiosName.substring(idxSep + 1);
        validateNotEmpty(domain, "%s=[%s] is invalid: domain suffix in netbios name canot be empty.");
    }

    public static String validateIdsUserName(String userName, IdentityStoreType idsType,
            AuthenticationType authType) {
        // userName is used to bind to IDP
        // (1) Doing a kerberos bind, userName is in upn@REALM format
        // (2) Doing a simple bind to OpenLdap and vmware directory userName is in DN format
        // (3) Doing a simple bind to AD over LDAP userName can be in DN format, upn or NetBios\\userName
        if (authType == AuthenticationType.PASSWORD) {
            if (isValidDNFormat(userName)) {
                return userName;
            } else if (idsType != IdentityStoreType.IDENTITY_STORE_TYPE_LDAP_WITH_AD_MAPPING) {
                logAndThrow(String.format("userName [%s] is not in valid DN format ", userName));
            }

            // validate userName to be UPN format
            try {
                validateUpn(userName, "userName in UPN format");
            } catch (Exception e) {
                // validate userName to be Netbios\\userName
                validateNetBios(userName, "userName in Netbios format");
            }
            return userName;
        } else if (authType == AuthenticationType.USE_KERBEROS) {
            try {
                return normalizeIdsKrbUserName(userName);
            } catch (Exception e) {
                logAndThrow(String.format("userName [%s] is not in valid kerberos UPN format ", userName));
            }
        } else {
            logAndThrow(String.format("Unsupported IDS authentication type"));
        }

        return null;
    }

    // Simple validation of DN format. True:  succeeded in validation.
    // This method is not enforcing non-emptiness.
    public static void validateDNFormat(String dnString) {
        if (!isValidDNFormat(dnString)) {
            throw new IllegalArgumentException(String.format("dnString [%s] is not in valid DN format ", dnString));
        }
    }

    // RFC 4512: keystring(descr), numericoid
    private static Pattern attributeTypePattern = Pattern.compile(
            "(([a-zA-Z]([a-zA-Z0-9\\x2D])*)|(([0-9]|([1-9]([0-9])+))(\\x2E([0-9]|([1-9]([0-9])+)))+))([ ])*\\=");

    // we will not validate the whole dn format syntax at the moment, but will check that the string comtain at least one instance of
    // <attributeType>= sequence
    public static boolean isValidDNFormat(String dnString) {
        boolean isValid = false;
        if (dnString != null && !dnString.isEmpty()) {
            Matcher m = attributeTypePattern.matcher(dnString);
            isValid = m.find();
        }
        return isValid;
    }

    /**
     * Validate extended IdentityStoreData attributes that should be in DN format.
     *
     * The attributes are:
     *  userName format validation
     *  userBaseDN format validation
     *  groupBaseDN format validation
     *
     * @param idsData
     */
    public static void validateIdsUserNameAndBaseDN(IIdentityStoreData idsData) {
        if (idsData.getDomainType().equals(DomainType.LOCAL_OS_DOMAIN))
            return;

        IIdentityStoreDataEx idsEx = idsData.getExtendedIdentityStoreData();
        if (null == idsEx) {
            return;
        }

        if (!idsData.getExtendedIdentityStoreData().useMachineAccount()) {
            validateIdsUserName(idsEx.getUserName(), idsData.getExtendedIdentityStoreData().getProviderType(),
                    idsData.getExtendedIdentityStoreData().getAuthenticationType());
        }

        // for AD IWA, skip groupBaseDN and userBaseDN check.
        if (!(idsData.getExtendedIdentityStoreData()
                .getProviderType() == IdentityStoreType.IDENTITY_STORE_TYPE_ACTIVE_DIRECTORY
                && idsData.getExtendedIdentityStoreData()
                        .getAuthenticationType() == AuthenticationType.USE_KERBEROS)) {
            String groupBaseDN = idsEx.getGroupBaseDn();
            try {
                validateDNFormat(groupBaseDN);
            } catch (Exception e) {
                logAndThrow(String.format("groupBaseDN [%s] is not in valid DN format ", groupBaseDN));
            }

            String userBaseDN = idsEx.getUserBaseDn();
            try {
                validateDNFormat(userBaseDN);
            } catch (Exception e) {
                logAndThrow(String.format("userBaseDN [%s] is not in valid DN format ", userBaseDN));
            }
        }
    }

    public static int getValidIdxAccountNameSeparator(String userPrincipal, char[] validAccountNameSep) {
        // separator scan
        // validate there is no more than one separator is used
        int latestResult = -1; // not found
        int totalSepCount = 0;
        for (char sep : validAccountNameSep) {
            int count = StringUtils.countMatches(userPrincipal, String.valueOf(sep));
            totalSepCount += count;
            if (totalSepCount > 1) {
                logAndThrow(String.format(
                        "Invalid user principal format [%s]: only one separator of '@' or '\' is allowed.",
                        userPrincipal));
            }
            if (count == 1) {
                latestResult = userPrincipal.indexOf(sep);
            }
        }

        return latestResult;
    }

    public static String getCanonicalUpnSuffix(String upnSuffix) {
        String resSuffix = null;
        if (upnSuffix != null) {
            resSuffix = upnSuffix.toUpperCase();
        }
        return resSuffix;
    }

    private static void logAndThrow(String msg) {
        DiagnosticsLoggerFactory.getLogger(ValidateUtil.class).error(msg);
        throw new IllegalArgumentException(msg);
    }

    private static IDiagnosticsLogger getLog() {
        return DiagnosticsLoggerFactory.getLogger(ValidateUtil.class);
    }

    private ValidateUtil() {
        // prevent instantiation
    }
}