org.cesecore.certificates.endentity.ExtendedInformation.java Source code

Java tutorial

Introduction

Here is the source code for org.cesecore.certificates.endentity.ExtendedInformation.java

Source

/*************************************************************************
 *                                                                       *
 *  CESeCore: CE Security Core                                           *
 *                                                                       *
 *  This software is free software; you can redistribute it and/or       *
 *  modify it under the terms of the GNU Lesser General Public           *
 *  License as published by the Free Software Foundation; either         *
 *  version 2.1 of the License, or any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/
package org.cesecore.certificates.endentity;

import java.io.Serializable;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.apache.log4j.Logger;
import org.bouncycastle.util.encoders.Base64;
import org.cesecore.certificates.crl.RevokedCertInfo;
import org.cesecore.internal.InternalResources;
import org.cesecore.internal.UpgradeableDataHashMap;
import org.cesecore.util.ValidityDate;

/**
 * The model representation of Extended Information about a user. It's used for non-searchable data about a user, 
 * like a image, in an effort to minimize the need for database alterations
 * 
 * TODO: Clean out whatever final static variables which aren't externally references.
 * 
 * @version $Id: ExtendedInformation.java 20558 2015-01-15 20:06:56Z jeklund $
 *
 */
public class ExtendedInformation extends UpgradeableDataHashMap implements Serializable {

    public static final String TYPE = "type";
    /** Different types of implementations of extended information, can be used to have different implementing classes of extended information */
    public static final int TYPE_BASIC = 0;

    private static final Logger log = Logger.getLogger(ExtendedInformation.class);
    /** Internal localization of logs and errors */
    private static final InternalResources intres = InternalResources.getInstance();

    private static final long serialVersionUID = 3981761824188420320L;

    private static final float LATEST_VERSION = 4;

    /**
     * Used to store subject directory attributes, which are put in an extension in the certificate. SubjectDirectoryAttributes are standard
     * attributes, see rfc3280
     */
    public static final String SUBJECTDIRATTRIBUTES = "subjectdirattributes";
    /**
     * the revocation code identifier primarily used in the XKMS protocol to let the end user revoke his certificate see the XKMS specification
     */
    public static final String XKMSREVOCATIONCODEIDENTIFIER = "revocationcodeidentifier";
    /** Custom data can be used by various custom work-flows and other non-standard things to store information needed */
    public static final String CUSTOMDATA = "customdata_";

    /**
     * Extension data can be used by the BasicCertificateExtension or custom 
     * certificate extensions to store data to be used when creating the 
     * extension such as the extension value. 
     */
    public static final String EXTENSIONDATA = "extensiondata_";

    /**
     * Identifier for Custom data holding a end time when the users certificate should be valid extInfo.setCustomData(EndEntityProfile.STARTTIME, "");
     */
    public static final String CUSTOM_STARTTIME = "STARTTIME"; // EndEntityProfile.STARTTIME;
    /**
     * Identifier for Custom data holding a end time when the users certificate should be valid extInfo.setCustomData(EndEntityProfile.ENDTIME, "");
     */
    public static final String CUSTOM_ENDTIME = "ENDTIME"; // EndEntityProfile.ENDTIME;

    /** The (optional) revocation status a certificate issued to this user will have, immediately upon issuance. */
    public static final String CUSTOM_REVOCATIONREASON = "REVOCATIONREASON";

    /** The subject DN exactly as requested in the UserDataVOWS object. */
    public static final String RAWSUBJECTDN = "RAWSUBJECTDN";

    /** The counter is a counter for how many failed login attempts that can be performed before the userstatus is changed to GENERATED */
    private static final String REMAININGLOGINATTEMPTS = "remainingloginattempts";

    /** The maximum number of login attempts before the user is locked by setting its status to GENERATED */
    private static final String MAXFAILEDLOGINATTEMPTS = "maxfailedloginattempts";

    /** Default value for how many failed login attempts are allow = -1 (unlimited) */
    public static final int DEFAULT_MAXLOGINATTEMPTS = -1;

    /** Default value for how many of the allowed failed login attempts that are remaining = -1 (unlimited) */
    public static final int DEFAULT_REMAININGLOGINATTEMPTS = -1;

    /** Map key for certificate serial number */
    private static final String CERTIFICATESERIALNUMBER = "CERTIFICATESERIALNUMBER";
    private static final Object NAMECONSTRAINTS_PERMITTED = "nameconstraints_permitted";
    private static final Object NAMECONSTRAINTS_EXCLUDED = "nameconstraints_excluded";

    /** Creates a new instance of EndEntity Profile */
    public ExtendedInformation() {
        setType(TYPE_BASIC);
        data.put(SUBJECTDIRATTRIBUTES, "");
        setMaxLoginAttempts(DEFAULT_MAXLOGINATTEMPTS);
        setRemainingLoginAttempts(DEFAULT_REMAININGLOGINATTEMPTS);
    }

    public String getSubjectDirectoryAttributes() {
        String ret = (String) data.get(SUBJECTDIRATTRIBUTES);
        if (ret == null) {
            ret = "";
        }
        return ret;
    }

    public void setSubjectDirectoryAttributes(String subjdirattr) {
        if (subjdirattr == null) {
            data.put(SUBJECTDIRATTRIBUTES, "");
        } else {
            data.put(SUBJECTDIRATTRIBUTES, subjdirattr);
        }
    }

    /**
     * Returns the revocation code identifier primarily used in the XKMS protocol to let the end user revoke his certificate.
     * 
     * 
     * The method is autoupgradable
     * 
     * @returns The code or null if no revocationcode have been set.
     */
    public String getRevocationCodeIdentifier() {
        String retval = (String) data.get(XKMSREVOCATIONCODEIDENTIFIER);
        return retval;
    }

    /**
     * 
     * 
     * @param revocationCodeIdentifier
     *            the string saved
     */
    public void setRevocationCodeIdentifier(String revocationCodeIdentifier) {
        String value = revocationCodeIdentifier;

        data.put(XKMSREVOCATIONCODEIDENTIFIER, value);

    }

    /**
     * @return The number of remaining allowed failed login attempts or -1 for unlimited
     */
    public int getRemainingLoginAttempts() {
        return ((Integer) data.get(REMAININGLOGINATTEMPTS)).intValue();
    }

    /**
     * Set the number of remaining login attempts. -1 means unlimited.
     * 
     * @param remainingLoginAttempts
     *            The number to set
     */
    public void setRemainingLoginAttempts(int remainingLoginAttempts) {
        data.put(REMAININGLOGINATTEMPTS, Integer.valueOf(remainingLoginAttempts));
    }

    /**
     * @return The maximum number of allowed failed login attempts or -1 for unlimited
     */
    public int getMaxLoginAttempts() {
        return ((Integer) data.get(MAXFAILEDLOGINATTEMPTS)).intValue();
    }

    /**
     * Set the number of maximum allowed failed login attempts. -1 means unlimited.
     * 
     * @param remainingLoginAttempts
     *            The number to set
     */
    public void setMaxLoginAttempts(int maxLoginAttempts) {
        data.put(MAXFAILEDLOGINATTEMPTS, Integer.valueOf(maxLoginAttempts));
    }

    /**
     * @return the serial number to be used for the certificate or null if no number defined.
     */
    public BigInteger certificateSerialNumber() {
        final String s = (String) this.data.get(CERTIFICATESERIALNUMBER);
        if (s == null) {
            return null;
        }
        return new BigInteger(Base64.decode(s));
    }

    /**
     * @param sn
     *            the serial number to be used for the certificate
     */
    public void setCertificateSerialNumber(BigInteger sn) {
        if (sn == null) {
            this.data.remove(CERTIFICATESERIALNUMBER);
            return;
        }
        final String s = new String(Base64.encode(sn.toByteArray()));
        this.data.put(CERTIFICATESERIALNUMBER, s);
    }

    /**
     * Returns the issuance revocation code configured on the end entity extended information.
     * 
     * @param data user data
     * @return issuance revocation code configured on the end entity extended information, a constant from RevokedCertInfo. Default
     *         RevokedCertInfo.NOT_REVOKED.
     */
    public int getIssuanceRevocationReason() {
        int ret = RevokedCertInfo.NOT_REVOKED;
        final String revocationReason = getCustomData(ExtendedInformation.CUSTOM_REVOCATIONREASON);
        if (revocationReason != null) {
            ret = Integer.valueOf(revocationReason);
        }
        if (log.isDebugEnabled()) {
            log.debug("User issuance revocation reason is " + ret);
        }
        return ret;
    }

    /**
    * Sets the issuance revocation code configured on the end entity extended information.
    * 
    * @param reason issuance revocation code, a constant from RevokedCertInfo such as RevokedCertInfo.REVOCATION_REASON_CERTIFICATEHOLD.
    */
    public void setIssuanceRevocationReason(int reason) {
        setCustomData(ExtendedInformation.CUSTOM_REVOCATIONREASON, "" + reason);
    }

    /** @return Encoded name constraints to permit */
    public List<String> getNameConstraintsPermitted() {
        String value = (String) data.get(NAMECONSTRAINTS_PERMITTED);
        if (value == null || value.isEmpty()) {
            return null;
        }
        return new ArrayList<String>(Arrays.asList(value.split(";")));
    }

    public void setNameConstraintsPermitted(List<String> encodedNames) {
        if (encodedNames == null) {
            data.remove(NAMECONSTRAINTS_PERMITTED);
        } else {
            data.put(NAMECONSTRAINTS_PERMITTED, StringUtils.join(encodedNames, ';'));
        }
    }

    /** @return Encoded name constraints to exclude */
    public List<String> getNameConstraintsExcluded() {
        String value = (String) data.get(NAMECONSTRAINTS_EXCLUDED);
        if (value == null || value.isEmpty()) {
            return null;
        }
        return new ArrayList<String>(Arrays.asList(value.split(";")));
    }

    public void setNameConstraintsExcluded(List<String> encodedNames) {
        if (encodedNames == null) {
            data.remove(NAMECONSTRAINTS_EXCLUDED);
        } else {
            data.put(NAMECONSTRAINTS_EXCLUDED, StringUtils.join(encodedNames, ';'));
        }
    }

    /** @return the subject DN exactly as requested (via WS ) */
    public String getRawSubjectDn() {
        final String value = (String) data.get(RAWSUBJECTDN);
        if (value == null || value.isEmpty()) {
            return null;
        }
        return value;
    }

    /**
     * Gets generic string data from the ExtendedInformation map.
     */
    public String getMapData(String key) {
        String ret = null;
        Object o = data.get(key);
        if (o instanceof String) {
            ret = (String) o;
        }
        return ret;
    }

    /**
     * Sets generic string data in the ExtendedInformation map.
     */
    public void setMapData(String key, String value) {
        data.put(key, value);
    }

    /**
     * Special method used to retrieve custom set userdata
     * 
     * @returns The data or null if no such data have been set for the user
     */
    public String getCustomData(String key) {
        String retval = (String) data.get(CUSTOMDATA + key);
        return retval;
    }

    /**
     * Sets extension data.
     * @param customly defined key to store the data with
     * @param the string representation of the data
     */
    public void setExtensionData(String key, String value) {
        data.put(EXTENSIONDATA + key, value);
    }

    /**
     * Special method used to retrieve custom extension data.
     * @returns The data or null if no such data have been set for the user
     */
    public String getExtensionData(String key) {
        String retval = (String) data.get(EXTENSIONDATA + key);
        return retval;
    }

    /**
     * 
     * @param customly
     *            defined key to store the data with
     * @param the
     *            string representation of the data
     */
    public void setCustomData(String key, String value) {
        data.put(CUSTOMDATA + key, value);
    }

    /** Function required by XMLEncoder to do a proper serialization. */
    public void setData(Object hmData) {
        loadData(hmData);
    }

    /** Function required by XMLEncoder to do a proper serialization. */
    public Object getData() {
        return saveData();
    }

    /** Implementation of UpgradableDataHashMap function getLatestVersion */
    public float getLatestVersion() {
        return LATEST_VERSION;
    }

    /** Implementation of UpgradableDataHashMap function upgrade. */

    public void upgrade() {
        if (Float.compare(LATEST_VERSION, getVersion()) != 0) {
            // New version of the class, upgrade
            String msg = intres.getLocalizedMessage("endentity.extendedinfoupgrade", new Float(getVersion()));
            log.info(msg);

            if (data.get(SUBJECTDIRATTRIBUTES) == null) {
                data.put(SUBJECTDIRATTRIBUTES, "");
            }
            if (data.get(MAXFAILEDLOGINATTEMPTS) == null) {
                setMaxLoginAttempts(DEFAULT_MAXLOGINATTEMPTS);
            }
            if (data.get(REMAININGLOGINATTEMPTS) == null) {
                setRemainingLoginAttempts(DEFAULT_REMAININGLOGINATTEMPTS);
            }
            // In EJBCA 4.0.0 we changed the date format
            if (getVersion() < 3) {
                final DateFormat oldDateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT,
                        Locale.US);
                final FastDateFormat newDateFormat = FastDateFormat.getInstance("yyyy-MM-dd HH:mm");
                try {
                    final String oldCustomStartTime = getCustomData(ExtendedInformation.CUSTOM_STARTTIME);
                    if (!isEmptyOrRelative(oldCustomStartTime)) {
                        // We use an absolute time format, so we need to upgrade
                        final String newCustomStartTime = newDateFormat
                                .format(oldDateFormat.parse(oldCustomStartTime));
                        setCustomData(ExtendedInformation.CUSTOM_STARTTIME, newCustomStartTime);
                        if (log.isDebugEnabled()) {
                            log.debug("Upgraded " + ExtendedInformation.CUSTOM_STARTTIME + " from \""
                                    + oldCustomStartTime + "\" to \"" + newCustomStartTime
                                    + "\" in ExtendedInformation.");
                        }
                    }
                } catch (ParseException e) {
                    log.error("Unable to upgrade " + ExtendedInformation.CUSTOM_STARTTIME
                            + " in extended user information.", e);
                }
                try {
                    final String oldCustomEndTime = getCustomData(ExtendedInformation.CUSTOM_ENDTIME);
                    if (!isEmptyOrRelative(oldCustomEndTime)) {
                        // We use an absolute time format, so we need to upgrade
                        final String newCustomEndTime = newDateFormat.format(oldDateFormat.parse(oldCustomEndTime));
                        setCustomData(ExtendedInformation.CUSTOM_ENDTIME, newCustomEndTime);
                        if (log.isDebugEnabled()) {
                            log.debug(
                                    "Upgraded " + ExtendedInformation.CUSTOM_ENDTIME + " from \"" + oldCustomEndTime
                                            + "\" to \"" + newCustomEndTime + "\" in ExtendedInformation.");
                        }
                    }
                } catch (ParseException e) {
                    log.error("Unable to upgrade " + ExtendedInformation.CUSTOM_ENDTIME
                            + " in extended user information.", e);
                }
            }
            // In 4.0.2 we further specify the storage format by saying that UTC TimeZone is implied instead of local server time
            if (getVersion() < 4) {
                final String[] timePatterns = { "yyyy-MM-dd HH:mm" };
                final String oldStartTime = getCustomData(ExtendedInformation.CUSTOM_STARTTIME);
                if (!isEmptyOrRelative(oldStartTime)) {
                    try {
                        final String newStartTime = ValidityDate
                                .formatAsUTC(DateUtils.parseDateStrictly(oldStartTime, timePatterns));
                        setCustomData(ExtendedInformation.CUSTOM_STARTTIME, newStartTime);
                        if (log.isDebugEnabled()) {
                            log.debug("Upgraded " + ExtendedInformation.CUSTOM_STARTTIME + " from \"" + oldStartTime
                                    + "\" to \"" + newStartTime + "\" in EndEntityProfile.");
                        }
                    } catch (ParseException e) {
                        log.error("Unable to upgrade " + ExtendedInformation.CUSTOM_STARTTIME
                                + " to UTC in EndEntityProfile! Manual interaction is required (edit and verify).",
                                e);
                    }
                }
                final String oldEndTime = getCustomData(ExtendedInformation.CUSTOM_ENDTIME);
                if (!isEmptyOrRelative(oldEndTime)) {
                    // We use an absolute time format, so we need to upgrade
                    try {
                        final String newEndTime = ValidityDate
                                .formatAsUTC(DateUtils.parseDateStrictly(oldEndTime, timePatterns));
                        setCustomData(ExtendedInformation.CUSTOM_ENDTIME, newEndTime);
                        if (log.isDebugEnabled()) {
                            log.debug("Upgraded " + ExtendedInformation.CUSTOM_ENDTIME + " from \"" + oldEndTime
                                    + "\" to \"" + newEndTime + "\" in EndEntityProfile.");
                        }
                    } catch (ParseException e) {
                        log.error("Unable to upgrade " + ExtendedInformation.CUSTOM_ENDTIME
                                + " to UTC in EndEntityProfile! Manual interaction is required (edit and verify).",
                                e);
                    }
                }
            }
            data.put(VERSION, Float.valueOf(LATEST_VERSION));
        }
    }

    /** @return true if argument is null, empty or in the relative time format. */
    private boolean isEmptyOrRelative(final String time) {
        return (time == null || time.length() == 0 || time.matches("^\\d+:\\d?\\d:\\d?\\d$"));
    }

    /**
     * Method that returns the classpath to the this or inheriting classes.
     * 
     * @return String containing the classpath.
     */
    public int getType() {
        return ((Integer) data.get(TYPE)).intValue();
    }

    /**
     * Method used to specify which kind of object that should be created during deserialization process.
     * 
     * Inheriting class should call 'setClassPath(this) in it's constructor.
     * 
     * @param object
     */
    private void setType(int type) {
        data.put(TYPE, type);
    }
}