/** * $RCSfile$ * $Revision: 3191 $ * $Date: 2005-12-12 13:41:22 -0300 (Mon, 12 Dec 2005) $ * * Copyright (C) 2005-2008 Jive Software. All rights reserved. * * Licensed under the Apache 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://www.apache.org/licenses/LICENSE-2.0 * * 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.jivesoftware.openfire.ldap; import java.text.MessageFormat; import java.util.Collection; import java.util.Collections; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; 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 javax.naming.ldap.LdapName; import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.group.Group; import org.jivesoftware.openfire.group.GroupNotFoundException; import org.jivesoftware.openfire.group.GroupProvider; import org.jivesoftware.openfire.user.UserManager; import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.util.JiveConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmpp.packet.JID; /** * LDAP implementation of the GroupProvider interface. All data in the directory is treated as * read-only so any set operations will result in an exception. * * @author Matt Tucker, Greg Ferguson and Cameron Moore */ public class LdapGroupProvider implements GroupProvider { private static final Logger Log = LoggerFactory.getLogger(LdapGroupProvider.class); private LdapManager manager; private UserManager userManager; private String[] standardAttributes; private int groupCount = -1; private long expiresStamp = System.currentTimeMillis(); /** * Constructs a new LDAP group provider. */ public LdapGroupProvider() { manager = LdapManager.getInstance(); userManager = UserManager.getInstance(); standardAttributes = new String[3]; standardAttributes[0] = manager.getGroupNameField(); standardAttributes[1] = manager.getGroupDescriptionField(); standardAttributes[2] = manager.getGroupMemberField(); } /** * Always throws an UnsupportedOperationException because LDAP groups are read-only. * * @param name the name of the group to create. * @throws UnsupportedOperationException when called. */ public Group createGroup(String name) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } /** * Always throws an UnsupportedOperationException because LDAP groups are read-only. * * @param name the name of the group to delete * @throws UnsupportedOperationException when called. */ public void deleteGroup(String name) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } public Group getGroup(String groupName) throws GroupNotFoundException { LdapContext ctx = null; try { String groupDN = manager.findGroupDN(groupName); // Load record. ctx = manager.getContext(manager.getGroupsBaseDN(groupName)); Attributes attrs = ctx.getAttributes(groupDN, standardAttributes); return processGroup(ctx, attrs); } catch (Exception e) { Log.error(e.getMessage(), e); throw new GroupNotFoundException("Group with name " + groupName + " not found.", e); } finally { try { if (ctx != null) { ctx.setRequestControls(null); ctx.close(); } } catch (Exception ignored) { // Ignore. } } } /** * Always throws an UnsupportedOperationException because LDAP groups are read-only. * * @param oldName the current name of the group. * @param newName the desired new name of the group. * @throws UnsupportedOperationException when called. */ public void setName(String oldName, String newName) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } /** * Always throws an UnsupportedOperationException because LDAP groups are read-only. * * @param name the group name. * @param description the group description. * @throws UnsupportedOperationException when called. */ public void setDescription(String name, String description) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } public int getGroupCount() { if (manager.isDebugEnabled()) { Log.debug("LdapGroupProvider: Trying to get the number of groups in the system."); } // Cache user count for 5 minutes. if (groupCount != -1 && System.currentTimeMillis() < expiresStamp) { return groupCount; } this.groupCount = manager.retrieveListCount( manager.getGroupNameField(), MessageFormat.format(manager.getGroupSearchFilter(), "*") ); this.expiresStamp = System.currentTimeMillis() + JiveConstants.MINUTE *5; return this.groupCount; } public Collection<String> getSharedGroupsNames() { // Get the list of shared groups from the database return Group.getSharedGroupsNames(); } public Collection<String> getGroupNames() { return getGroupNames(-1, -1); } public Collection<String> getGroupNames(int startIndex, int numResults) { return manager.retrieveList( manager.getGroupNameField(), MessageFormat.format(manager.getGroupSearchFilter(), "*"), startIndex, numResults, null ); } public Collection<String> getGroupNames(JID user) { // Get DN of specified user XMPPServer server = XMPPServer.getInstance(); String username; if (!manager.isPosixMode()) { // Check if the user exists (only if user is a local user) if (!server.isLocal(user)) { return Collections.emptyList(); } username = JID.unescapeNode(user.getNode()); try { username = manager.findUserDN(username) + "," + manager.getUsersBaseDN(username); } catch (Exception e) { Log.error("Could not find user in LDAP " + username); return Collections.emptyList(); } } else { username = server.isLocal(user) ? JID.unescapeNode(user.getNode()) : user.toString(); } // Do nothing if the user is empty or null if (username == null || "".equals(username)) { return Collections.emptyList(); } StringBuilder filter = new StringBuilder(); filter.append("(&"); filter.append(MessageFormat.format(manager.getGroupSearchFilter(), "*")); filter.append("(").append(manager.getGroupMemberField()).append("=").append(username); filter.append("))"); if (Log.isDebugEnabled()) { Log.debug("Trying to find group names for user: " + user + " using query: " + filter.toString()); } // Perform the LDAP query return manager.retrieveList( manager.getGroupNameField(), filter.toString(), -1, -1, null ); } /** * Always throws an UnsupportedOperationException because LDAP groups are read-only. * * @param groupName name of a group. * @param user the JID of the user to add * @param administrator true if is an administrator. * @throws UnsupportedOperationException when called. */ public void addMember(String groupName, JID user, boolean administrator) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } /** * Always throws an UnsupportedOperationException because LDAP groups are read-only. * * @param groupName the naame of a group. * @param user the JID of the user with new privileges * @param administrator true if is an administrator. * @throws UnsupportedOperationException when called. */ public void updateMember(String groupName, JID user, boolean administrator) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } /** * Always throws an UnsupportedOperationException because LDAP groups are read-only. * * @param groupName the name of a group. * @param user the JID of the user to delete. * @throws UnsupportedOperationException when called. */ public void deleteMember(String groupName, JID user) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } /** * Returns true because LDAP groups are read-only. * * @return true because all LDAP functions are read-only. */ public boolean isReadOnly() { return true; } public Collection<String> search(String query) { return search(query, -1, -1); } public Collection<String> search(String query, int startIndex, int numResults) { if (query == null || "".equals(query)) { return Collections.emptyList(); } // Make the query be a wildcard search by default. So, if the user searches for // "Test", make the search be "Test*" instead. if (!query.endsWith("*")) { query = query + "*"; } StringBuilder filter = new StringBuilder(); filter.append("(").append(manager.getGroupNameField()).append("=").append(query).append(")"); // Perform the LDAP query return manager.retrieveList( manager.getGroupNameField(), filter.toString(), startIndex, numResults, null ); } public boolean isSearchSupported() { return true; } private Group processGroup(LdapContext ctx, Attributes a) throws NamingException { XMPPServer server = XMPPServer.getInstance(); String serverName = server.getServerInfo().getXMPPDomain(); // Build `3 groups. // group 1: uid= // group 2: rest of the text until first comma // group 3: rest of the text Pattern pattern = Pattern.compile("(?i)(^" + manager.getUsernameField() + "=)([^,]+)(.+)"); SearchControls searchControls = new SearchControls(); searchControls.setReturningAttributes(new String[] { manager.getUsernameField() }); // See if recursive searching is enabled. Otherwise, only search one level. if (manager.isSubTreeSearch()) { searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); } else { searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE); } String name; String description; try { name = ((String)((a.get(manager.getGroupNameField())).get())); } catch (Exception e) { name = ""; } try { description = ((String)((a.get(manager.getGroupDescriptionField())).get())); } catch (Exception e) { description = ""; } Set<JID> members = new TreeSet<JID>(); Attribute memberField = a.get(manager.getGroupMemberField()); if (memberField != null) { NamingEnumeration ne = memberField.getAll(); while (ne.hasMore()) { String username = (String) ne.next(); // If not posix mode, each group member is stored as a full DN. if (!manager.isPosixMode()) { try { // Try to find the username with a regex pattern match. Matcher matcher = pattern.matcher(username); if (matcher.matches() && matcher.groupCount() == 3) { // The username is in the DN, no additional search needed username = matcher.group(2); } // The regex pattern match failed. This will happen if the // the member DN's don't use the standard username field. For // example, Active Directory has a username field of // sAMAccountName, but stores group members as "CN=...". else { // Create an LDAP name with the full DN. LdapName ldapName = new LdapName(username); // Turn the LDAP name into something we can use in a // search by stripping off the comma. StringBuilder userFilter = new StringBuilder(); userFilter.append("(&("); userFilter.append(ldapName.get(ldapName.size() - 1)); userFilter.append(")"); userFilter.append(MessageFormat.format(manager.getSearchFilter(), "*")); userFilter.append(")"); NamingEnumeration usrAnswer = ctx.search("", userFilter.toString(), searchControls); if (usrAnswer != null && usrAnswer.hasMoreElements()) { Attribute usernameAttr = ((SearchResult)usrAnswer.next()).getAttributes().get(manager.getUsernameField()); if (usernameAttr != null) { username = (String)usernameAttr.get(); } } // Close the enumeration. usrAnswer.close(); } } catch (Exception e) { // TODO: A NPE is occuring here Log.error(e.getMessage(), e); } } // A search filter may have been defined in the LdapUserProvider. // Therefore, we have to try to load each user we found to see if // it passes the filter. try { JID userJID; int position = username.indexOf("@" + serverName); // Create JID of local user if JID does not match a component's JID if (position == -1) { // In order to lookup a username from the manager, the username // must be a properly escaped JID node. String escapedUsername = JID.escapeNode(username); if (!escapedUsername.equals(username)) { // Check if escaped username is valid userManager.getUser(escapedUsername); } // No exception, so the user must exist. Add the user as a group // member using the escaped username. userJID = server.createJID(escapedUsername, null); } else { // This is a JID of a component or node of a server's component String node = username.substring(0, position); String escapedUsername = JID.escapeNode(node); userJID = new JID(escapedUsername + "@" + serverName); } members.add(userJID); } catch (UserNotFoundException e) { // We can safely ignore this error. It likely means that // the user didn't pass the search filter that's defined. // So, we want to simply ignore the user as a group member. if (manager.isDebugEnabled()) { Log.debug("LdapGroupProvider: User not found: " + username); } } } // Close the enumeration. ne.close(); } if (manager.isDebugEnabled()) { Log.debug("LdapGroupProvider: Adding group \"" + name + "\" with " + members.size() + " members."); } Collection<JID> admins = Collections.emptyList(); return new Group(name, description, members, admins); } }