All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opendaylight.aaa.shiro.realm.ODLJndiLdapRealm Maven / Gradle / Ivy

There is a newer version: 0.20.3
Show newest version
/*
 * Copyright (c) 2015 - 2017 Brocade Communications Systems, Inc. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */

package org.opendaylight.aaa.shiro.realm;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapContext;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.ldap.DefaultLdapRealm;
import org.apache.shiro.realm.ldap.LdapContextFactory;
import org.apache.shiro.realm.ldap.LdapUtils;
import org.apache.shiro.subject.PrincipalCollection;
import org.opendaylight.aaa.shiro.realm.mapping.api.GroupsToRolesMappingStrategy;
import org.opendaylight.aaa.shiro.realm.mapping.impl.BestAttemptGroupToRolesMappingStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An extended implementation of
 * org.apache.shiro.realm.ldap.JndiLdapRealm which includes
 * additional Authorization capabilities.  To enable this Realm, add the
 * following to shiro.ini:
 *
 * 

* #ldapRealm = ODLJndiLdapRealmAuthNOnly * #ldapRealm.userDnTemplate = uid={0},ou=People,dc=DOMAIN,dc=TLD * #ldapRealm.contextFactory.url = ldap://URL:389 * #ldapRealm.searchBase = dc=DOMAIN,dc=TLD * #ldapRealm.ldapAttributeForComparison = objectClass * # The CSV list of enabled realms. In order to enable a realm, add it to the * # list below: * securityManager.realms = $tokenAuthRealm, $ldapRealm * *

* The values above are specific to the deployed LDAP domain. If the defaults * are not sufficient, alternatives can be derived through enabling * TRACE level logging. To enable TRACE level * logging, issue the following command in the karaf shell: * log:set TRACE ODLJndiLdapRealm * * @see org.apache.shiro.realm.ldap.JndiLdapRealm * @see Shiro * documentation */ public class ODLJndiLdapRealm extends DefaultLdapRealm { private static final Logger LOG = LoggerFactory.getLogger(ODLJndiLdapRealm.class); /** * When an LDAP Authorization lookup is made for a user account, a list of * attributes are returned. The attributes are used to determine LDAP * grouping, which is equivalent to ODL role(s). The default value is * set to "objectClass", which is common attribute for LDAP systems. * The actual value may be configured through setting * ldapAttributeForComparison. */ private static final String DEFAULT_LDAP_ATTRIBUTE_FOR_COMPARISON = "objectClass"; /** * The LDAP nomenclature for user ID, which is used in the authorization query process. */ private static final String UID = "uid"; /** * When multiple roles are specified in groupRolesMap, this delimiter separates the individual roles. */ private static final String ROLE_NAMES_DELIMITER = ","; /** * Strategy to determine how groups are mapped to roles. */ private static final GroupsToRolesMappingStrategy GROUPS_TO_ROLES_MAPPING_STRATEGY = new BestAttemptGroupToRolesMappingStrategy(); /** * The searchBase for the ldap query, which indicates the LDAP realms to * search. By default, this is set to the * super.getUserDnSuffix(). */ private String searchBase = super.getUserDnSuffix(); /** * When an LDAP Authorization lookup is made for a user account, a list of * attributes is returned. The attributes are used to determine LDAP * grouping, which is equivalent to ODL role(s). The default is set to * DEFAULT_LDAP_ATTRIBUTE_FOR_COMPARISON. */ private String ldapAttributeForComparison = DEFAULT_LDAP_ATTRIBUTE_FOR_COMPARISON; private Map groupRolesMap; /* * Adds debugging information surrounding creation of ODLJndiLdapRealm */ public ODLJndiLdapRealm() { LOG.debug("Creating ODLJndiLdapRealm"); } /* * (non-Javadoc) Overridden to expose important audit trail information for * accounting. * * @see * org.apache.shiro.realm.ldap.JndiLdapRealm#doGetAuthenticationInfo(org * .apache.shiro.authc.AuthenticationToken) */ @Override protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token) throws AuthenticationException { // Delegates all AuthN lookup responsibility to the super class try { final String username = getUsername(token); logIncomingConnection(username); return super.doGetAuthenticationInfo(token); } catch (ClassCastException e) { LOG.info("Couldn't service the LDAP connection", e); return null; } } /** * Logs an incoming LDAP connection. * * @param username the requesting user */ protected void logIncomingConnection(final String username) { LOG.info("AAA LDAP connection from {}", username); } /** * Extracts the username from token. * * @param token Encoded token which could contain a username * @return The extracted username * @throws ClassCastException The incoming token is not username/password (i.e., X.509 * certificate) */ public static String getUsername(final AuthenticationToken token) throws ClassCastException { return token == null ? null : (String) token.getPrincipal(); } /** * extracts a username from principals. * * @param principals A single principal extracted for the username * @return The username if possible * @throws ClassCastException the PrincipalCollection contains an element that is not in * username/password form (i.e., X.509 certificate) */ protected String getUsername(final PrincipalCollection principals) throws ClassCastException { return principals == null ? null : (String) getAvailablePrincipal(principals); } @Override protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) { AuthorizationInfo ai = null; try { ai = this.queryForAuthorizationInfo(principals, getContextFactory()); } catch (NamingException e) { LOG.error("Unable to query for AuthZ info", e); } return ai; } /* * (non-Javadoc) * * This method is only called if doGetAuthenticationInfo(...) completes successfully AND * the requested endpoint has an RBAC restriction. To add an RBAC restriction, edit the * etc/shiro.ini file and add a url to the url section. E.g., * * /** = authcBasic, roles[person] * * @see org.apache.shiro.realm.ldap.JndiLdapRealm#queryForAuthorizationInfo(org.apache.shiro.subject * .PrincipalCollection, org.apache.shiro.realm.ldap.LdapContextFactory) */ @Override protected AuthorizationInfo queryForAuthorizationInfo(final PrincipalCollection principals, final LdapContextFactory ldapContextFactory) throws NamingException { AuthorizationInfo authorizationInfo = null; try { final String username = getUsername(principals); final LdapContext ldapContext = ldapContextFactory.getSystemLdapContext(); final Set roleNames; try { roleNames = getRoleNamesForUser(username, ldapContext); authorizationInfo = buildAuthorizationInfo(roleNames); } finally { LdapUtils.closeContext(ldapContext); } } catch (ClassCastException e) { LOG.error("Unable to extract a valid user", e); } return authorizationInfo; } public static AuthorizationInfo buildAuthorizationInfo(final Set roleNames) { return roleNames == null ? null : new SimpleAuthorizationInfo(roleNames); } /** * extracts the Set of roles associated with a user based on the username * and ldap context (server). * * @param username The username for the request * @param ldapContext The specific system context provided by shiro.ini * @return A set of roles * @throws NamingException If the ldap search fails */ protected Set getRoleNamesForUser(final String username, final LdapContext ldapContext) throws NamingException { final Set roleNames = new LinkedHashSet<>(); final SearchControls searchControls = createSearchControls(); LOG.debug("Asking the configured LDAP about which groups uid=\"{}\" belongs to using " + "searchBase=\"{}\" ldapAttributeForComparison=\"{}\"", username, searchBase, ldapAttributeForComparison); final NamingEnumeration answer = ldapContext .search(searchBase, String.format("%s=%s", UID, username), searchControls); while (answer.hasMoreElements()) { final SearchResult searchResult = answer.next(); final Attributes attrs = searchResult.getAttributes(); if (attrs != null) { final NamingEnumeration ae = attrs.getAll(); while (ae.hasMore()) { final Attribute attr = ae.next(); LOG.debug("LDAP returned \"{}\" attribute for \"{}\"", attr.getID(), username); if (attr.getID().equals(ldapAttributeForComparison)) { final Collection groupNamesExtractedFromLdap = LdapUtils.getAllAttributeValues(attr); final Map> groupsToRoles = GROUPS_TO_ROLES_MAPPING_STRATEGY .mapGroupsToRoles(groupNamesExtractedFromLdap, ROLE_NAMES_DELIMITER, groupRolesMap); final Collection roleNamesFromLdapGroups; // map the groups if (groupRolesMap != null) { roleNamesFromLdapGroups = new HashSet<>(); for (Set roles : groupsToRoles.values()) { roleNamesFromLdapGroups.addAll(roles); } if (LOG.isDebugEnabled()) { for (Entry> entry : groupsToRoles.entrySet()) { LOG.debug("Mapped the \"{}\" LDAP group to \"{}\" ODL role for \"{}\"", entry.getKey(), entry.getValue(), username); } } } else { LOG.debug("Since groupRolesMap was unspecified, no mapping is attempted so " + "the role names are set to the extracted group names"); roleNamesFromLdapGroups = groupNamesExtractedFromLdap; if (LOG.isDebugEnabled()) { for (String group : groupNamesExtractedFromLdap) { LOG.debug("Mapped the \"{}\" LDAP group to \"{}\" ODL role for \"{}\"", group, group, username); } } } roleNames.addAll(roleNamesFromLdapGroups); } } } } return roleNames; } /** * A utility method to help create the search controls for the LDAP lookup. * * @return A generic set of search controls for LDAP scoped to subtree */ protected static SearchControls createSearchControls() { SearchControls searchControls = new SearchControls(); searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); return searchControls; } @Override public String getUserDnSuffix() { return super.getUserDnSuffix(); } /** * Injected from shiro.ini configuration. * * @param searchBase The desired value for searchBase */ public void setSearchBase(final String searchBase) { // public for injection reasons this.searchBase = searchBase; } /** * Injected from shiro.ini configuration. * * @param ldapAttributeForComparison The attribute from which groups are extracted */ public void setLdapAttributeForComparison(final String ldapAttributeForComparison) { // public for injection reasons this.ldapAttributeForComparison = ldapAttributeForComparison; } /** * Injected from shiro.ini configuration. * * @param groupRolesMap Something like "ldapAdmin":"admin,user","organizationalPerson":"user" */ public void setGroupRolesMap(final Map groupRolesMap) { this.groupRolesMap = groupRolesMap; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy