/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at:
*
* http://opensource.org/licenses/ecl2.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package org.opencastproject.kernel.userdirectory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/** Map a series of LDAP attributes to user authorities in Opencast */
public class LdapAttributeAuthoritiesPopulator implements LdapAuthoritiesPopulator {
private Set<String> attributeNames;
private String prefix = "";
private boolean uppercase = true;
private Set<String> additionalAuthorities;
private static final Logger logger = LoggerFactory.getLogger(LdapAttributeAuthoritiesPopulator.class);
/**
* Build a new authorities populator
*
* This assumes that the authenticated users contain an attribute consisting of a space- or comma-separated list of
* authorities
*
* @param attributeNames
* The LDAP user attribute containing the roles
*/
public LdapAttributeAuthoritiesPopulator(Collection<? extends String> attributeNames) {
if (attributeNames == null) {
throw new NullPointerException("The attribute list cannot be null");
}
if (attributeNames.size() == 0) {
throw new IllegalArgumentException("At least one attribute must be provided");
}
this.attributeNames = new HashSet<String>(attributeNames);
this.additionalAuthorities = new HashSet<String>();
}
@Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for (String attributeName : attributeNames) {
try {
String[] attributeValues = userData.getStringAttributes(attributeName);
// Should the attribute not be defined, the returned array is null
if (attributeValues != null) {
for (String attributeValue : attributeValues) {
// The attribute value may be a single authority (a single role) or a list of roles
addAuthorities(authorities, attributeValue.split(","));
}
} else {
logger.debug("Could not find any attribute named '{}' in user '{}'", attributeName, userData.getDn());
}
} catch (ClassCastException e) {
logger.error("Specified attribute containing user roles ('{}') was not of expected type String: {}",
attributeName, e);
}
}
// Add the additional authorities
addAuthorities(authorities, additionalAuthorities.toArray(new String[additionalAuthorities.size()]));
return authorities;
}
/**
* Return the attributes names this object will search for
*
* @return a {@link Collection} containing such attribute names
*/
public Collection<String> getAttributeNames() {
return new HashSet<String>(this.attributeNames);
}
/**
* Define the prefix that should be appended to all the roles obtained.
*
* The prefix is empty by default.
*
* @param prefix
* A {@link String} containing the desired role prefix.
*
*/
public void setRolePrefix(String prefix) {
this.prefix = (prefix == null) ? "" : prefix;
}
/**
* Get the role prefix being used by this object. Please note that such prefix can be empty.
*
* @return the role prefix in use.
*/
public String getRolePrefix() {
return prefix;
}
/**
* Set whether or not to convert the role names to uppercase.
*
* Initially, this property defaults to {@code true}.
*
* @param convert
* {@code true} to convert the role names to uppercase. {@code false} otherwise.
*/
public void setConvertToUpperCase(boolean convert) {
uppercase = convert;
}
/**
* Get the property that defines whether or not the role names should be converted to uppercase.
*
* @return {@code true} if this class converts the role names to uppercase. {@code false} otherwise.
*/
public boolean getConvertToUpperCase() {
return uppercase;
}
/**
* Define the set of additional authorities that are appended to the authority list of every authenticated user.
*
* The authorities must not be null nor empty.
*
* @param authorities
* a {@link Collection} of {@link String}s representing the additional authorities.
*/
public void setAdditionalAuthorities(Set<? extends String> authorities) {
if (authorities == null) {
additionalAuthorities.clear();
} else {
additionalAuthorities = new HashSet<String>(authorities);
}
}
/**
* Return the set of additional authorities, that are appended to the authority list of every authenticated user.
*
* @return a {@link Collection} of {@link String}s representing the additional authorities.
*/
public Set<? extends String> getAdditionalAuthorities() {
return new HashSet<String>(additionalAuthorities);
}
private void addAuthorities(Set<GrantedAuthority> authorities, String[] values) {
for (String value : values) {
String authority = parseAuthority(value);
// Ignore the empty parts
if (!authority.isEmpty()) {
authorities.add(new SimpleGrantedAuthority(authority));
}
}
}
/**
* Processes a {@link String} representing a {@link GrantedAuthority} to make it comply with the expected format
*
* In particular, the method adds a prefix (if defined), capitalises the {@String}, substitutes the white spaces by
* underscores ("_") and collapses any series of underscores into a single one.
*
* @param authority
* A {@code String} representing the authority
* @return a {@code String} representing the authority in a standard format
*/
private String parseAuthority(String authority) {
// Trim the authority
// We do not substitute the whitespace here because we must make sure that the prefix follows the spacing
// conventions, too
authority = authority.trim();
// Add prefix only if the authority is not empty
if (!authority.isEmpty()) {
if (uppercase) {
return (this.prefix.trim() + authority).replaceAll("[\\s_]+", "_").toUpperCase();
} else {
return (this.prefix.trim() + authority).replaceAll("[\\s_]+", "_");
}
}
return authority;
}
}