package org.apereo.cas.services;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apereo.cas.authentication.principal.DefaultPrincipalAttributesRepository;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.PrincipalAttributesRepository;
import org.apereo.cas.authentication.principal.Service;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.util.spring.ApplicationContextProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* Abstract release policy for attributes, provides common shared settings such as loggers and attribute filter config.
* Subclasses are to provide the behavior for attribute retrieval.
*
* @author Misagh Moayyed
* @since 4.1.0
*/
public abstract class AbstractRegisteredServiceAttributeReleasePolicy implements RegisteredServiceAttributeReleasePolicy {
private static final long serialVersionUID = 5325460875620586503L;
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRegisteredServiceAttributeReleasePolicy.class);
private RegisteredServiceAttributeFilter registeredServiceAttributeFilter;
private PrincipalAttributesRepository principalAttributesRepository = new DefaultPrincipalAttributesRepository();
private boolean authorizedToReleaseCredentialPassword;
private boolean authorizedToReleaseProxyGrantingTicket;
private boolean excludeDefaultAttributes;
private String principalIdAttribute;
@Override
public void setAttributeFilter(final RegisteredServiceAttributeFilter filter) {
this.registeredServiceAttributeFilter = filter;
}
public void setPrincipalAttributesRepository(final PrincipalAttributesRepository repository) {
this.principalAttributesRepository = repository;
}
public PrincipalAttributesRepository getPrincipalAttributesRepository() {
return this.principalAttributesRepository;
}
public RegisteredServiceAttributeFilter getAttributeFilter() {
return this.registeredServiceAttributeFilter;
}
public String getPrincipalIdAttribute() {
return principalIdAttribute;
}
public void setPrincipalIdAttribute(final String principalIdAttribute) {
this.principalIdAttribute = principalIdAttribute;
}
@Override
public boolean isAuthorizedToReleaseCredentialPassword() {
return this.authorizedToReleaseCredentialPassword;
}
@Override
public boolean isAuthorizedToReleaseProxyGrantingTicket() {
return this.authorizedToReleaseProxyGrantingTicket;
}
public void setAuthorizedToReleaseCredentialPassword(final boolean authorizedToReleaseCredentialPassword) {
this.authorizedToReleaseCredentialPassword = authorizedToReleaseCredentialPassword;
}
public void setAuthorizedToReleaseProxyGrantingTicket(final boolean authorizedToReleaseProxyGrantingTicket) {
this.authorizedToReleaseProxyGrantingTicket = authorizedToReleaseProxyGrantingTicket;
}
public boolean isExcludeDefaultAttributes() {
return excludeDefaultAttributes;
}
public void setExcludeDefaultAttributes(final boolean excludeDefaultAttributes) {
this.excludeDefaultAttributes = excludeDefaultAttributes;
}
@Override
public Map<String, Object> getAttributes(final Principal principal, final Service selectedService,
final RegisteredService registeredService) {
LOGGER.debug("Locating principal attributes for [{}]", principal.getId());
final Map<String, Object> principalAttributes = getPrincipalAttributesRepository() == null
? principal.getAttributes() : getPrincipalAttributesRepository().getAttributes(principal);
LOGGER.debug("Found principal attributes [{}] for [{}]", principalAttributes, principal.getId());
LOGGER.debug("Calling attribute policy [{}] to process attributes for [{}]", getClass().getSimpleName(), principal.getId());
final Map<String, Object> policyAttributes = getAttributesInternal(principal, principalAttributes, registeredService);
LOGGER.debug("Attribute policy [{}] allows release of [{}] for [{}]", getClass().getSimpleName(), policyAttributes, principal.getId());
LOGGER.debug("Attempting to merge policy attributes and default attributes");
final Map<String, Object> attributesToRelease = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
if (isExcludeDefaultAttributes()) {
LOGGER.debug("Ignoring default attribute policy attributes");
} else {
LOGGER.debug("Checking default attribute policy attributes");
final Map<String, Object> defaultAttributes = getReleasedByDefaultAttributes(principal, principalAttributes);
LOGGER.debug("Default attributes found to be released are [{}]", defaultAttributes);
LOGGER.debug("Adding default attributes first to the released set of attributes");
attributesToRelease.putAll(defaultAttributes);
}
LOGGER.debug("Adding policy attributes to the released set of attributes");
attributesToRelease.putAll(policyAttributes);
insertPrincipalIdAsAttributeIfNeeded(principal, attributesToRelease, selectedService, registeredService);
if (getAttributeFilter() != null) {
LOGGER.debug("Invoking attribute filter [{}] on the final set of attributes", getAttributeFilter());
return getAttributeFilter().filter(attributesToRelease);
}
return returnFinalAttributesCollection(attributesToRelease, registeredService);
}
/**
* Release principal id as attribute if needed.
*
* @param principal the principal
* @param attributesToRelease the attributes to release
* @param service the service
* @param registeredService the registered service
*/
protected void insertPrincipalIdAsAttributeIfNeeded(final Principal principal,
final Map<String, Object> attributesToRelease,
final Service service,
final RegisteredService registeredService) {
if (StringUtils.isNotBlank(getPrincipalIdAttribute())) {
LOGGER.debug("Attempting to resolve the principal id for service [{}]", registeredService.getServiceId());
final String id = registeredService.getUsernameAttributeProvider().resolveUsername(principal, service, registeredService);
LOGGER.debug("Releasing resolved principal id [{}] as attribute [{}]", id, getPrincipalIdAttribute());
attributesToRelease.put(getPrincipalIdAttribute(), principal.getId());
}
}
/**
* Return the final attributes collection.
* Subclasses may override this minute to impose last minute rules.
*
* @param attributesToRelease the attributes to release
* @param service the service
* @return the map
*/
protected Map<String, Object> returnFinalAttributesCollection(final Map<String, Object> attributesToRelease,
final RegisteredService service) {
LOGGER.debug("Final collection of attributes allowed are: [{}]", attributesToRelease);
return attributesToRelease;
}
/**
* Determines a default bundle of attributes that may be released to all services
* without the explicit mapping for each service.
*
* @param p the principal
* @param attributes the attributes
* @return the released by default attributes
*/
protected Map<String, Object> getReleasedByDefaultAttributes(final Principal p, final Map<String, Object> attributes) {
final ApplicationContext ctx = ApplicationContextProvider.getApplicationContext();
if (ctx != null) {
LOGGER.debug("Located application context. Retrieving default attributes for release, if any");
final CasConfigurationProperties props = ctx.getAutowireCapableBeanFactory().getBean(CasConfigurationProperties.class);
final Set<String> defaultAttrs = props.getAuthn().getAttributeRepository().getDefaultAttributesToRelease();
LOGGER.debug("Default attributes for release are: [{}]", defaultAttrs);
final Map<String, Object> defaultAttributesToRelease = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
defaultAttrs.stream().forEach(key -> {
if (attributes.containsKey(key)) {
LOGGER.debug("Found and added default attribute for release: [{}]", key);
defaultAttributesToRelease.put(key, attributes.get(key));
}
});
return defaultAttributesToRelease;
}
return new TreeMap<>();
}
/**
* Gets the attributes internally from the implementation.
*
* @param principal the principal
* @param attributes the principal attributes
* @param service the service
* @return the attributes allowed for release
*/
protected abstract Map<String, Object> getAttributesInternal(Principal principal,
Map<String, Object> attributes,
RegisteredService service);
@Override
public int hashCode() {
return new HashCodeBuilder(13, 133)
.append(getAttributeFilter())
.append(isAuthorizedToReleaseCredentialPassword())
.append(isAuthorizedToReleaseProxyGrantingTicket())
.append(getPrincipalAttributesRepository())
.append(isExcludeDefaultAttributes())
.append(getPrincipalIdAttribute())
.toHashCode();
}
@Override
public boolean equals(final Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
if (!(o instanceof AbstractRegisteredServiceAttributeReleasePolicy)) {
return false;
}
final AbstractRegisteredServiceAttributeReleasePolicy that = (AbstractRegisteredServiceAttributeReleasePolicy) o;
final EqualsBuilder builder = new EqualsBuilder();
return builder
.append(getAttributeFilter(), that.getAttributeFilter())
.append(isAuthorizedToReleaseCredentialPassword(), that.isAuthorizedToReleaseCredentialPassword())
.append(isAuthorizedToReleaseProxyGrantingTicket(), that.isAuthorizedToReleaseProxyGrantingTicket())
.append(getPrincipalAttributesRepository(), that.getPrincipalAttributesRepository())
.append(isExcludeDefaultAttributes(), that.isExcludeDefaultAttributes())
.append(getPrincipalIdAttribute(), that.getPrincipalIdAttribute())
.isEquals();
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("attributeFilter", getAttributeFilter())
.append("principalAttributesRepository", getPrincipalAttributesRepository())
.append("authorizedToReleaseCredentialPassword", isAuthorizedToReleaseCredentialPassword())
.append("authorizedToReleaseProxyGrantingTicket", isAuthorizedToReleaseProxyGrantingTicket())
.append("excludeDefaultAttributes", isExcludeDefaultAttributes())
.append("principalIdAttribute", getPrincipalIdAttribute())
.toString();
}
}