Java tutorial
/* * RHQ Management Platform * Copyright (C) 2005-2011 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.server.resource.group; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.InvalidSearchFilterException; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.InitialLdapContext; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.authz.Permission; import org.rhq.core.domain.authz.Role; import org.rhq.core.domain.common.composite.SystemSetting; import org.rhq.core.domain.resource.group.LdapGroup; import org.rhq.core.domain.util.PageControl; import org.rhq.core.domain.util.PageList; import org.rhq.core.server.PersistenceUtility; import org.rhq.enterprise.server.RHQConstants; import org.rhq.enterprise.server.auth.SubjectManagerLocal; import org.rhq.enterprise.server.authz.RequiredPermission; import org.rhq.enterprise.server.exception.LdapCommunicationException; import org.rhq.enterprise.server.exception.LdapFilterException; import org.rhq.enterprise.server.system.SystemManagerLocal; import org.rhq.enterprise.server.util.security.UntrustedSSLSocketFactory; /** * This bean provides functionality to manipulate the ldap auth/authz functionality. * That is, adding/modifying/deleting ldap group/users and their * associated subjects and permissions are performed by this manager. * * @author paji * @author Simeon Pinder */ @Stateless public class LdapGroupManagerBean implements LdapGroupManagerLocal { private Log log = LogFactory.getLog(LdapGroupManagerBean.class); private static final String BASEDN_DELIMITER = ";"; @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME) private EntityManager entityManager; @EJB private SubjectManagerLocal subjectManager; @EJB private SystemManagerLocal systemManager; public Set<Map<String, String>> findAvailableGroups() { Properties systemConfig = systemManager.getSystemConfiguration(subjectManager.getOverlord()); Set<Map<String, String>> emptyAvailableGroups = new HashSet<Map<String, String>>(); //retrieve the filters. String groupFilter = (String) systemConfig.get(RHQConstants.LDAPGroupFilter); if ((groupFilter != null) && (!groupFilter.trim().isEmpty())) { String filter = String.format("(%s)", groupFilter); return buildGroup(systemConfig, filter); } return emptyAvailableGroups; } public Set<String> findAvailableGroupsFor(String userName) { Properties options = systemManager.getSystemConfiguration(subjectManager.getOverlord()); String groupFilter = options.getProperty(RHQConstants.LDAPGroupFilter, ""); String groupMember = options.getProperty(RHQConstants.LDAPGroupMember, ""); String userDN = getUserDN(options, userName); Set<String> ldapSet = new HashSet<String>(); if (userDN != null && userDN.trim().length() > 0) { //TODO: spinder 4/21/10 put in error/debug logging messages for badly formatted filter combinations String filter = ""; //form assumes examples where groupFilter is like 'objectclass=groupOfNames' and groupMember is 'member' // to produce ldap filter like (&(objectclass=groupOfNames)(member=cn=Administrator,ou=People,dc=test,dc=com)) filter = String.format("(&(%s)(%s=%s))", groupFilter, groupMember, LDAPStringUtil.encodeForFilter(userDN)); Set<Map<String, String>> matched = buildGroup(options, filter); log.trace("Located '" + matched.size() + "' LDAP groups for user '" + userName + "' using following ldap filter '" + filter + "'."); //iterate to extract just the group names. for (Map<String, String> match : matched) { ldapSet.add(match.get("id")); } } else { log.debug("Group lookup will not be performed due to no UserDN found for user " + userName); } return ldapSet; } @RequiredPermission(Permission.MANAGE_SECURITY) public void setLdapGroupsOnRole(Subject subject, int roleId, Set<LdapGroup> groups) { Role role = entityManager.find(Role.class, roleId); if (role == null) { throw new IllegalArgumentException("Role with id [" + roleId + "] does not exist."); } Set<LdapGroup> currentGroups = role.getLdapGroups(); List<String> currentGroupNames = new ArrayList<String>(currentGroups.size()); for (LdapGroup group : currentGroups) { currentGroupNames.add(group.getName()); } List<String> newGroupNames = new ArrayList<String>(groups.size()); for (LdapGroup group : groups) { newGroupNames.add(group.getName()); } List<String> namesOfGroupsToBeAdded = new ArrayList<String>(newGroupNames); namesOfGroupsToBeAdded.removeAll(currentGroupNames); addLdapGroupsToRole(subject, roleId, namesOfGroupsToBeAdded); List<String> namesOfGroupsToBeRemoved = new ArrayList<String>(currentGroupNames); namesOfGroupsToBeRemoved.removeAll(newGroupNames); int[] idsOfGroupsToBeRemoved = new int[namesOfGroupsToBeRemoved.size()]; int i = 0; for (LdapGroup group : currentGroups) { if (namesOfGroupsToBeRemoved.contains(group.getName())) { idsOfGroupsToBeRemoved[i++] = group.getId(); } } removeLdapGroupsFromRole(subject, roleId, idsOfGroupsToBeRemoved); } @RequiredPermission(Permission.MANAGE_SECURITY) public void addLdapGroupsToRole(Subject subject, int roleId, List<String> groupNames) { if ((groupNames != null) && (groupNames.size() > 0)) { Role role = entityManager.find(Role.class, roleId); if (role == null) { throw new IllegalArgumentException("Could not find role[" + roleId + "] to add LDAP groups to."); } role.getLdapGroups().size(); // load them in for (String groupId : groupNames) { LdapGroup group = new LdapGroup(); group.setName(groupId); role.addLdapGroup(group); } } } @RequiredPermission(Permission.MANAGE_SECURITY) public void removeLdapGroupsFromRole(Subject subject, int roleId, int[] groupIds) { if ((groupIds != null) && (groupIds.length > 0)) { Role role = entityManager.find(Role.class, roleId); if (role == null) { throw new IllegalArgumentException( "Could not find role[" + roleId + "] to remove LDAP groups from."); } role.getLdapGroups().size(); // load them in for (Integer groupId : groupIds) { LdapGroup doomedGroup = entityManager.find(LdapGroup.class, groupId); if (doomedGroup == null) { throw new IllegalArgumentException("Tried to remove doomedGroup[" + groupId + "] from role[" + roleId + "], but doomedGroup was not found."); } role.removeLdapGroup(doomedGroup); } Query purgeQuery = entityManager.createNamedQuery(LdapGroup.DELETE_BY_ID); List<Integer> ids = new LinkedList<Integer>(); for (int i : groupIds) { ids.add(i); } purgeQuery.setParameter("ids", ids); purgeQuery.executeUpdate(); } } private List<Role> findRolesByLdapGroupNames(List<String> ldapGroupNames) { if (ldapGroupNames.isEmpty()) { return Collections.EMPTY_LIST; } Query query = entityManager.createNamedQuery(LdapGroup.FIND_BY_ROLES_GROUP_NAMES); query.setParameter("names", ldapGroupNames); return (List<Role>) query.getResultList(); } public void assignRolesToLdapSubject(int subjectId, List<String> ldapGroupNames) { Subject sub = entityManager.find(Subject.class, subjectId); List<Role> roles = findRolesByLdapGroupNames(ldapGroupNames); sub.getRoles().clear(); sub.getLdapRoles().clear(); for (Role role : roles) { sub.addRole(role); sub.addLdapRole(role); } } public PageList<LdapGroup> findLdapGroupsByRole(int roleId, PageControl pageControl) { Role role = entityManager.find(Role.class, roleId); if (role == null) { throw new IllegalArgumentException("Could not find role[" + roleId + "] to lookup ldap Groups on"); } return new PageList<LdapGroup>(role.getLdapGroups(), role.getLdapGroups().size(), pageControl); } public PageList<LdapGroup> findLdapGroups(PageControl pc) { pc.initDefaultOrderingField("g.name"); String queryName = LdapGroup.QUERY_FIND_ALL; Query queryCount = PersistenceUtility.createCountQuery(entityManager, queryName); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, queryName, pc); long count = (Long) queryCount.getSingleResult(); List<LdapGroup> groups = query.getResultList(); return new PageList<LdapGroup>(groups, (int) count, pc); } /**Build/retrieve the user DN. Not usually a property. * * @param options * @param userName * @return */ private String getUserDN(Properties options, String userName) { Map<String, String> details = findLdapUserDetails(userName); String userDN = details.get("dn"); return userDN; } public Map<String, String> findLdapUserDetails(String userName) { Properties systemConfig = systemManager.getSystemConfiguration(subjectManager.getOverlord()); HashMap<String, String> userDetails = new HashMap<String, String>(); // Load our LDAP specific properties Properties env = getProperties(systemConfig); // Load the BaseDN String baseDN = (String) systemConfig.get(RHQConstants.LDAPBaseDN); // Load the LoginProperty String loginProperty = (String) systemConfig.get(RHQConstants.LDAPLoginProperty); if (loginProperty == null) { // Use the default loginProperty = "cn"; } // Load any information we may need to bind String bindDN = (String) systemConfig.get(RHQConstants.LDAPBindDN); String bindPW = (String) systemConfig.get(RHQConstants.LDAPBindPW); // Load any search filter String searchFilter = (String) systemConfig.get(RHQConstants.LDAPFilter); if (bindDN != null) { env.setProperty(Context.SECURITY_PRINCIPAL, bindDN); env.setProperty(Context.SECURITY_CREDENTIALS, bindPW); env.setProperty(Context.SECURITY_AUTHENTICATION, "simple"); } try { InitialLdapContext ctx = new InitialLdapContext(env, null); SearchControls searchControls = getSearchControls(); // Add the search filter if specified. This only allows for a single search filter.. i.e. foo=bar. String filter; if ((searchFilter != null) && (searchFilter.length() != 0)) { filter = "(&(" + loginProperty + "=" + userName + ")" + "(" + searchFilter + "))"; } else { filter = "(" + loginProperty + "=" + userName + ")"; } log.debug("Using LDAP filter [" + filter + "] to locate user details for " + userName); // Loop through each configured base DN. It may be useful // in the future to allow for a filter to be configured for // each BaseDN, but for now the filter will apply to all. String[] baseDNs = baseDN.split(BASEDN_DELIMITER); for (int x = 0; x < baseDNs.length; x++) { NamingEnumeration<SearchResult> answer = ctx.search(baseDNs[x], filter, searchControls); if (!answer.hasMoreElements()) { //BZ:582471- ldap api bug change log.debug("User " + userName + " not found for BaseDN " + baseDNs[x]); // Nothing found for this DN, move to the next one if we have one. continue; } // We use the first match SearchResult si = answer.next(); //generate the DN String userDN = null; try { userDN = si.getNameInNamespace(); } catch (UnsupportedOperationException use) { userDN = si.getName(); if (userDN.startsWith("\"")) { userDN = userDN.substring(1, userDN.length()); } if (userDN.endsWith("\"")) { userDN = userDN.substring(0, userDN.length() - 1); } userDN = userDN + "," + baseDNs[x]; } userDetails.put("dn", userDN); // Construct the UserDN NamingEnumeration<String> keys = si.getAttributes().getIDs(); while (keys.hasMore()) { String key = keys.next(); Attribute value = si.getAttributes().get(key); if ((value != null) && (value.get() != null)) { userDetails.put(key, value.get().toString()); } } return userDetails; } return userDetails; } catch (NamingException e) { throw new RuntimeException(e); } } /** * @throws NamingException * @see org.jboss.security.auth.spi.UsernamePasswordLoginModule#validatePassword(java.lang.String,java.lang.String) */ protected Set<Map<String, String>> buildGroup(Properties systemConfig, String filter) { Set<Map<String, String>> ret = new HashSet<Map<String, String>>(); // Load our LDAP specific properties Properties env = getProperties(systemConfig); // Load the BaseDN String baseDN = (String) systemConfig.get(RHQConstants.LDAPBaseDN); // Load the LoginProperty String loginProperty = (String) systemConfig.get(RHQConstants.LDAPLoginProperty); if (loginProperty == null) { // Use the default loginProperty = "cn"; } // Load any information we may need to bind String bindDN = (String) systemConfig.get(RHQConstants.LDAPBindDN); String bindPW = (String) systemConfig.get(RHQConstants.LDAPBindPW); if (bindDN != null) { env.setProperty(Context.SECURITY_PRINCIPAL, bindDN); env.setProperty(Context.SECURITY_CREDENTIALS, bindPW); env.setProperty(Context.SECURITY_AUTHENTICATION, "simple"); } try { InitialLdapContext ctx = new InitialLdapContext(env, null); SearchControls searchControls = getSearchControls(); /*String filter = "(&(objectclass=groupOfUniqueNames)(uniqueMember=uid=" + userName + ",ou=People, dc=rhndev, dc=redhat, dc=com))";*/ // Loop through each configured base DN. It may be useful // in the future to allow for a filter to be configured for // each BaseDN, but for now the filter will apply to all. String[] baseDNs = baseDN.split(BASEDN_DELIMITER); for (int x = 0; x < baseDNs.length; x++) { NamingEnumeration<SearchResult> answer = ctx.search(baseDNs[x], filter, searchControls); boolean ldapApiEnumerationBugEncountered = false; while ((!ldapApiEnumerationBugEncountered) && answer.hasMoreElements()) {//BZ:582471- ldap api bug change // We use the first match SearchResult si = null; try { si = answer.next(); } catch (NullPointerException npe) { ldapApiEnumerationBugEncountered = true; break; } Map<String, String> entry = new HashMap<String, String>(); String name = (String) si.getAttributes().get("cn").get(); name = name.trim(); Attribute desc = si.getAttributes().get("description"); String description = desc != null ? (String) desc.get() : ""; description = description.trim(); entry.put("id", name); entry.put("name", name); entry.put("description", description); ret.add(entry); } } } catch (NamingException e) { if (e instanceof InvalidSearchFilterException) { InvalidSearchFilterException fException = (InvalidSearchFilterException) e; String message = "The ldap group filter defined is invalid "; log.error(message, fException); throw new LdapFilterException(message + " " + fException.getMessage()); } //TODO: check for ldap connection/unavailable/etc. exceptions. else { log.error("LDAP communication error: " + e.getMessage(), e); throw new LdapCommunicationException(e); } } return ret; } /** * Load a default set of properties to use when connecting to the LDAP server. If basic authentication is needed, * the caller must set Context.SECURITY_PRINCIPAL, Context.SECURITY_CREDENTIALS and Context.SECURITY_AUTHENTICATION * appropriately. * * @return properties that are to be used when connecting to LDAP server */ private Properties getProperties(Properties systemConfig) { Properties env = new Properties(systemConfig); // Set our default factory name if one is not given String factoryName = env.getProperty(RHQConstants.LDAPFactory); env.setProperty(Context.INITIAL_CONTEXT_FACTORY, factoryName); // Setup SSL if requested String value = env.getProperty(SystemSetting.USE_SSL_FOR_LDAP.getInternalName()); boolean ldapSsl = "ssl".equalsIgnoreCase(value); if (ldapSsl) { String ldapSocketFactory = env.getProperty("java.naming.ldap.factory.socket"); if (ldapSocketFactory == null) { env.put("java.naming.ldap.factory.socket", UntrustedSSLSocketFactory.class.getName()); } env.put(Context.SECURITY_PROTOCOL, "ssl"); } // Set the LDAP url String providerUrl = env.getProperty(RHQConstants.LDAPUrl); if (providerUrl == null) { int port = (ldapSsl) ? 636 : 389; providerUrl = "ldap://localhost:" + port; } env.setProperty(Context.PROVIDER_URL, providerUrl); // Follow referrals automatically env.setProperty(Context.REFERRAL, "ignore"); //BZ:582471- active directory query change return env; } /** * A simple method to construct a SearchControls object for use when doing LDAP searches. All of the defaults are * used, with the exception of the scope, which is set to SUBTREE rather than the default of ONE_LEVEL * * @return controls what is searched in LDAP */ private SearchControls getSearchControls() { // Set the scope to subtree, default is one-level int scope = SearchControls.SUBTREE_SCOPE; // No limit on the time waiting for a response int timeLimit = 0; // No limit on the number of entries returned long countLimit = 0; // Attributes to return. String[] returnedAttributes = null; // Don't return the object boolean returnObject = false; // No dereferencing during the search boolean deference = false; SearchControls constraints = new SearchControls(scope, countLimit, timeLimit, returnedAttributes, returnObject, deference); return constraints; } }