package org.apereo.cas.services.support; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apereo.cas.services.RegisteredServiceAttributeFilter; import org.apereo.cas.util.CollectionUtils; import org.apereo.cas.util.RegexUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * A filtering policy that selectively applies patterns to attributes mapped in the config. * If an attribute is mapped, it's only allowed to be released if it matches the linked pattern. * If an attribute is not mapped, it may optionally be excluded from the released set of attributes. * * @author Misagh Moayyed * @since 5.1.0 */ public class RegisteredServiceMappedRegexAttributeFilter implements RegisteredServiceAttributeFilter { private static final long serialVersionUID = 852145306984610128L; private static final Logger LOGGER = LoggerFactory.getLogger(RegisteredServiceMappedRegexAttributeFilter.class); private Map<String, String> patterns; private boolean excludeUnmappedAttributes; private boolean completeMatch; private int order; public RegisteredServiceMappedRegexAttributeFilter() { } public RegisteredServiceMappedRegexAttributeFilter(final Map<String, String> patterns) { this.patterns = patterns; } @Override public Map<String, Object> filter(final Map<String, Object> givenAttributes) { final Map<String, Object> attributesToRelease = new HashMap<>(); givenAttributes.entrySet() .stream() .filter(entry -> { final String attributeName = entry.getKey(); final Object attributeValue = entry.getValue(); LOGGER.debug("Received attribute [{}] with value(s) [{}]", attributeName, attributeValue); return attributeValue != null; }) .forEach(entry -> { final String attributeName = entry.getKey(); if (patterns.containsKey(attributeName)) { final Set<Object> attributeValues = CollectionUtils.toCollection(entry.getValue()); final Pattern pattern = RegexUtils.createPattern(patterns.get(attributeName)); LOGGER.debug("Found attribute [{}] in the pattern definitions. Processing pattern [{}]", attributeName, pattern.pattern()); final List<Object> filteredValues = filterAttributeValuesByPattern(attributeValues, pattern); LOGGER.debug("Filtered attribute values for [{}] are [{}]", attributeName, filteredValues); if (filteredValues.isEmpty()) { LOGGER.debug("Attribute [{}] has no values remaining and shall be excluded", attributeName); } else { attributesToRelease.put(attributeName, filteredValues); } } else { LOGGER.debug("Found attribute [{}] that is not defined in pattern definitions", attributeName); if (excludeUnmappedAttributes) { LOGGER.debug("Excluding attribute [{}] given unmatched attributes are to be excluded", attributeName); } else { LOGGER.debug("Added unmatched attribute [{}] with value(s) [{}]", entry.getKey(), entry.getValue()); attributesToRelease.put(entry.getKey(), entry.getValue()); } } }); LOGGER.debug("Received [{}] attributes. Filtered and released [{}]", givenAttributes.size(), attributesToRelease.size()); return attributesToRelease; } /** * Filter attribute values by pattern and return the list. * * @param attributeValues the attribute values * @param pattern the pattern * @return the list */ protected List<Object> filterAttributeValuesByPattern(final Set<Object> attributeValues, final Pattern pattern) { return attributeValues.stream() .filter(v -> { final Matcher matcher = pattern.matcher(v.toString()); LOGGER.debug("Matching attribute value [{}] against pattern [{}]", v, pattern.pattern()); if (completeMatch) { return matcher.matches(); } return matcher.find(); }) .collect(Collectors.toList()); } @Override public int getOrder() { return order; } public void setOrder(final int order) { this.order = order; } public Map<String, String> getPatterns() { return patterns; } public void setPatterns(final Map<String, String> patterns) { this.patterns = patterns; } public boolean isExcludeUnmappedAttributes() { return excludeUnmappedAttributes; } public boolean isCompleteMatch() { return completeMatch; } public void setCompleteMatch(final boolean completeMatch) { this.completeMatch = completeMatch; } public void setExcludeUnmappedAttributes(final boolean excludeUnmappedAttributes) { this.excludeUnmappedAttributes = excludeUnmappedAttributes; } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (obj.getClass() != getClass()) { return false; } final RegisteredServiceMappedRegexAttributeFilter rhs = (RegisteredServiceMappedRegexAttributeFilter) obj; return new EqualsBuilder() .append(this.patterns, rhs.patterns) .append(this.excludeUnmappedAttributes, rhs.excludeUnmappedAttributes) .append(this.completeMatch, rhs.completeMatch) .append(this.order, rhs.order) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder() .append(patterns) .append(excludeUnmappedAttributes) .append(completeMatch) .append(order) .toHashCode(); } }