package org.apereo.cas.authentication.principal;

import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.util.EncodingUtils;
import org.apereo.cas.util.DigestUtils;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.io.ByteSource;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.charset.Charset;

/**
 * Generates PersistentIds based on the Shibboleth algorithm.
 *
 * @author Scott Battaglia
 * @since 3.1
 */
public class ShibbolethCompatiblePersistentIdGenerator implements PersistentIdGenerator {

    private static final long serialVersionUID = 6182838799563190289L;

    /** Log instance. */
    private static final Logger LOGGER = LoggerFactory.getLogger(ShibbolethCompatiblePersistentIdGenerator.class);

    private static final String CONST_SEPARATOR = "!";

    private static final int CONST_DEFAULT_SALT_COUNT = 16;

    @JsonProperty
    private String salt;

    /**
     * Instantiates a new shibboleth compatible persistent id generator.
     * The salt is initialized to a random 16-digit alphanumeric string.
     * The generated id is pseudo-anonymous which allows it to be continually uniquely
     * identified by for a particular service.
     */
    public ShibbolethCompatiblePersistentIdGenerator() {
        this.salt = RandomStringUtils.randomAlphanumeric(CONST_DEFAULT_SALT_COUNT);
    }

    public ShibbolethCompatiblePersistentIdGenerator(final String salt) {
        this.salt = salt;
    }

    private byte[] convertSaltToByteArray() {
        return this.salt.getBytes(Charset.defaultCharset());
    }

    /**
     * Get salt.
     *
     * @return the byte[] for the salt or null
     */
    @JsonIgnore
    public byte[] getSalt() {
        try {
            return ByteSource.wrap(convertSaltToByteArray()).read();
        } catch (final IOException e) {
            LOGGER.warn("Salt cannot be read because the byte array from source could not be consumed");
        }
        return null;
    }

    @Override
    public String generate(final Principal principal, final Service service) {
        final String data = String.join(CONST_SEPARATOR, service.getId(), principal.getId());
        final Charset charset = Charset.defaultCharset();
        final String result = EncodingUtils.encodeBase64(DigestUtils.sha(data.getBytes(charset)));
        return result.replaceAll(System.getProperty("line.separator"), StringUtils.EMPTY);
    }


    @Override
    public boolean equals(final Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj == this) {
            return true;
        }
        if (obj.getClass() != getClass()) {
            return false;
        }
        final ShibbolethCompatiblePersistentIdGenerator rhs = (ShibbolethCompatiblePersistentIdGenerator) obj;
        return new EqualsBuilder()
                .append(this.salt, rhs.salt)
                .isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder()
                .append(this.salt)
                .toHashCode();
    }
}

@@ -47,136 +47,8 @@
 l;%0A%0A
-import org.apache.commons.lang3.StringUtils;%0Aimport org.apereo.cas.util.EncodingUtils;%0Aimport org.apereo.cas.util.DigestUtils;%0A%0A
 impo
@@ -240,16 +240,61 @@
 gUtils;%0A
+import org.apache.commons.lang3.StringUtils;%0A
 import o
@@ -389,32 +389,171 @@
 ashCodeBuilder;%0A
+import org.apache.commons.lang3.builder.ToStringBuilder;%0Aimport org.apereo.cas.util.DigestUtils;%0Aimport org.apereo.cas.util.EncodingUtils;%0A
 import org.slf4j
@@ -722,16 +722,124 @@
 orithm.%0A
+ * The generated ids are based on a principal attribute is specified, or%0A * the authenticated principal id.%0A
  *%0A * @a
@@ -1048,16 +1048,23 @@
 %0A    /**
+%0A     *
  Log ins
@@ -1069,16 +1069,21 @@
 nstance.
+%0A    
  */%0A    
@@ -1355,16 +1355,65 @@
  salt;%0A%0A
+    @JsonProperty%0A    private String attribute;%0A%0A
     /**%0A
@@ -1964,108 +1964,160 @@
    p
-rivate byte%5B%5D convertSaltToByteArray() %7B%0A        return this.salt.getBytes(Charset.defaultCharset())
+ublic String getAttribute() %7B%0A        return attribute;%0A    %7D%0A%0A    public void setAttribute(final String attribute) %7B%0A        this.attribute = attribute
 ;%0A  
@@ -2307,31 +2307,51 @@
 rap(
-convertSaltToByteArray(
+this.salt.getBytes(Charset.defaultCharset()
 )).r
@@ -2629,24 +2629,251 @@
  service) %7B%0A
+        final String principalId = StringUtils.isNotBlank(this.attribute) && principal.getAttributes().containsKey(this.attribute)%0A                ? principal.getAttributes().get(this.attribute).toString() : principal.getId();%0A
         fina
@@ -2943,24 +2943,18 @@
 rincipal
-.getId()
+Id
 );%0A     
@@ -3010,30 +3010,24 @@
 ();%0A        
-final 
 String resul
@@ -3109,20 +3109,22 @@
       re
-turn
+sult =
  result.
@@ -3196,16 +3196,103 @@
 Y);%0A
+        LOGGER.debug(%22Generated persistent id is %5B%7B%7D%5D%22, result);%0A        return result;%0A
     %7D%0A%0A
-%0A
     
@@ -3735,32 +3735,87 @@
 salt, rhs.salt)%0A
+                .append(this.attribute, rhs.attribute)%0A
                 
@@ -3969,18 +3969,228 @@
    .
-toHashCode
+append(this.attribute)%0A                .toHashCode();%0A    %7D%0A%0A    @Override%0A    public String toString() %7B%0A        return new ToStringBuilder(this)%0A                .append(%22attribute%22, attribute)%0A                .toString
 ();%0A

Add support for attribute-based persistent id gentlemen