package org.apereo.cas.services;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.Service;
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.TreeMap;
/**
* Determines the username for this registered service based on a principal attribute.
* If the attribute is not found, default principal id is returned.
*
* @author Misagh Moayyed
* @since 4.1.0
*/
public class PrincipalAttributeRegisteredServiceUsernameProvider extends BaseRegisteredServiceUsernameAttributeProvider {
private static final long serialVersionUID = -3546719400741715137L;
private static final Logger LOGGER = LoggerFactory.getLogger(PrincipalAttributeRegisteredServiceUsernameProvider.class);
private String usernameAttribute;
/**
* Private constructor to get around serialization issues.
*/
private PrincipalAttributeRegisteredServiceUsernameProvider() {
this.usernameAttribute = null;
}
/**
* Instantiates a new default registered service username provider.
*
* @param usernameAttribute the username attribute
*/
public PrincipalAttributeRegisteredServiceUsernameProvider(final String usernameAttribute) {
this.usernameAttribute = usernameAttribute;
}
public PrincipalAttributeRegisteredServiceUsernameProvider(final String usernameAttribute, final String canonicalizationMode) {
super(canonicalizationMode);
this.usernameAttribute = usernameAttribute;
}
public String getUsernameAttribute() {
return this.usernameAttribute;
}
@Override
public String resolveUsernameInternal(final Principal principal, final Service service) {
String principalId = principal.getId();
final Map<String, Object> originalPrincipalAttributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
originalPrincipalAttributes.putAll(principal.getAttributes());
final Map<String, Object> attributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
attributes.putAll(getPrincipalAttributes(principal, service));
LOGGER.debug("Principal attributes available for selection of username attribute [{}] are [{}].", this.usernameAttribute, attributes);
if (attributes.containsKey(this.usernameAttribute)) {
principalId = attributes.get(this.usernameAttribute).toString();
} else if (originalPrincipalAttributes.containsKey(this.usernameAttribute)) {
LOGGER.warn("The selected username attribute [{}] was retrieved as a direct "
+ "principal attributes and not through the attribute release policy for service [{}]. "
+ "CAS is unable to detect new attribute values for [{}] after authentication unless the attribute "
+ "is explicitly authorized for release via the service attribute release policy.",
this.usernameAttribute, service, this.usernameAttribute);
principalId = originalPrincipalAttributes.get(this.usernameAttribute).toString();
} else {
LOGGER.warn("Principal [{}] does not have an attribute [{}] among attributes [{}] so CAS cannot "
+ "provide the user attribute the service expects. "
+ "CAS will instead return the default principal id [{}]. Ensure the attribute selected as the username "
+ "is allowed to be released by the service attribute release policy.",
principalId,
this.usernameAttribute,
attributes,
principalId);
}
LOGGER.debug("Principal id to return for [{}] is [{}]. The default principal id is [{}].",
service.getId(), principalId, principal.getId());
return principalId.trim();
}
@Override
public String toString() {
final ToStringBuilder toStringBuilder = new ToStringBuilder(null, ToStringStyle.SHORT_PREFIX_STYLE);
toStringBuilder.append("usernameAttribute", this.usernameAttribute);
return toStringBuilder.toString();
}
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (obj.getClass() != getClass()) {
return false;
}
final PrincipalAttributeRegisteredServiceUsernameProvider rhs =
(PrincipalAttributeRegisteredServiceUsernameProvider) obj;
return new EqualsBuilder()
.append(this.usernameAttribute, rhs.usernameAttribute)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder()
.append(this.usernameAttribute)
.toHashCode();
}
/**
* Gets principal attributes. Will attempt to locate the principal
* attribute repository from the context if one is defined to use
* that instance to locate attributes. If none is available,
* will use the default principal attributes.
*
* @param p the principal
* @param service the service
* @return the principal attributes
*/
protected Map<String, Object> getPrincipalAttributes(final Principal p, final Service service) {
final ApplicationContext context = ApplicationContextProvider.getApplicationContext();
if (context != null) {
LOGGER.debug("Located application context to locate the service registry entry");
final ServicesManager servicesManager = context.getBean(ServicesManager.class);
if (servicesManager != null) {
final RegisteredService registeredService = servicesManager.findServiceBy(service);
if (registeredService != null && registeredService.getAccessStrategy().isServiceAccessAllowed()) {
LOGGER.debug("Located service [{}] in the registry. Attempting to resolve attributes for [{}]",
registeredService, p.getId());
if (registeredService.getAttributeReleasePolicy() == null) {
LOGGER.debug("No attribute release policy is defined for [{}]. Returning default principal attributes", service.getId());
return p.getAttributes();
}
return registeredService.getAttributeReleasePolicy().getAttributes(p, service, registeredService);
}
}
LOGGER.debug("Could not locate service [{}] in the registry.", service.getId());
throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE);
}
LOGGER.warn("No application context could be detected. Returning default principal attributes");
return p.getAttributes();
}
public void setUsernameAttribute(final String usernameAttribute) {
this.usernameAttribute = usernameAttribute;
}
}