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

org.jivesoftware.openfire.ldap.LdapUserProvider Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2004-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.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimeZone;

import javax.naming.NamingEnumeration;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;

import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserCollection;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.openfire.user.UserProvider;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;

/**
 * LDAP implementation of the UserProvider interface. All data in the directory is
 * treated as read-only so any set operations will result in an exception.
 *
 * @author Matt Tucker
 */
public class LdapUserProvider implements UserProvider {

    private static final Logger Log = LoggerFactory.getLogger(LdapUserProvider.class);

    // LDAP date format parser.
    private static SimpleDateFormat ldapDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");

    private LdapManager manager;
    private Map searchFields;
    private int userCount = -1;
    private long expiresStamp = System.currentTimeMillis();

    public LdapUserProvider() {
        // Convert XML based provider setup to Database based
        JiveGlobals.migrateProperty("ldap.searchFields");

        manager = LdapManager.getInstance();
        searchFields = new LinkedHashMap<>();
        String fieldList = JiveGlobals.getProperty("ldap.searchFields");
        // If the value isn't present, default to to username, name, and email.
        if (fieldList == null) {
            searchFields.put("Username", manager.getUsernameField());
            searchFields.put("Name", manager.getNameField());
            searchFields.put("Email", manager.getEmailField());
        }
        else {
            try {
                for (StringTokenizer i=new StringTokenizer(fieldList, ","); i.hasMoreTokens(); ) {
                    String[] field = i.nextToken().split("/");
                    searchFields.put(field[0], field[1]);
                }
            }
            catch (Exception e) {
                Log.error("Error parsing LDAP search fields: " + fieldList, e);
            }
        }
    }

    @Override
    public User loadUser(String username) throws UserNotFoundException {
        if(username.contains("@")) {
            if (!XMPPServer.getInstance().isLocal(new JID(username))) {
                throw new UserNotFoundException("Cannot load user of remote server: " + username);
            }
            username = username.substring(0,username.lastIndexOf("@"));
        }
        // Un-escape username.
        username = JID.unescapeNode(username);
        DirContext ctx = null;
        try {
            String userDN = manager.findUserDN(username);
            // Load record.
            String[] attributes = new String[]{
                manager.getUsernameField(), manager.getNameField(),
                manager.getEmailField(), "createTimestamp", "modifyTimestamp"
            };
            ctx = manager.getContext(manager.getUsersBaseDN(username));
            Attributes attrs = ctx.getAttributes(userDN, attributes);
            String name = null;
            Attribute nameField = attrs.get(manager.getNameField());
            if (nameField != null) {
                name = (String)nameField.get();
            }
            String email = null;
            Attribute emailField = attrs.get(manager.getEmailField());
            if (emailField != null) {
                email = (String)emailField.get();
            }
            Date creationDate = new Date();
            Attribute creationDateField = attrs.get("createTimestamp");
            if (creationDateField != null && "".equals(((String) creationDateField.get()).trim())) {
                creationDate = parseLDAPDate((String) creationDateField.get());
            }
            Date modificationDate = new Date();
            Attribute modificationDateField = attrs.get("modifyTimestamp");
            if (modificationDateField != null && "".equals(((String) modificationDateField.get()).trim())) {
                modificationDate = parseLDAPDate((String)modificationDateField.get());
            }
            // Escape the username so that it can be used as a JID.
            username = JID.escapeNode(username);
            
            // As defined by RFC5803.
            Attribute authPassword = attrs.get("authPassword");
            User user = new User(username, name, email, creationDate, modificationDate);
            if (authPassword != null) {
                // The authPassword attribute can be multivalued.
                // Not sure if this is the right API to loop through them.
                NamingEnumeration values = authPassword.getAll();
                while (values.hasMore()) {
                    Attribute authPasswordValue = (Attribute) values.next();
                    String[] parts = ((String) authPasswordValue.get()).split("$");
                    String[] authInfo = parts[1].split(":");
                    String[] authValue = parts[2].split(":");
    
                    String scheme = parts[0].trim();
    
                    // We only support SCRAM-SHA-1 at the moment.
                    if ("SCRAM-SHA-1".equals(scheme)) {
                        int iterations = Integer.valueOf(authInfo[0].trim());
                        String salt = authInfo[1].trim();
                        String storedKey = authValue[0].trim();
                        String serverKey = authValue[1].trim();
                        
                        user.setSalt(salt);
                        user.setStoredKey(storedKey);
                        user.setServerKey(serverKey);
                        user.setIterations(iterations);
                        
                        break;
                    }
                }
            }
            return user;
        }
        catch (Exception e) {
            throw new UserNotFoundException(e);
        }
        finally {
            try {
                if (ctx != null) {
                    ctx.close();
                }
            }
            catch (Exception ignored) {
                // Ignore.
            }
        }
    }

    @Override
    public User createUser(String username, String password, String name, String email)
            throws UserAlreadyExistsException
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public void deleteUser(String username) {
        throw new UnsupportedOperationException();
    }

    @Override
    public int getUserCount() {
        // Cache user count for 5 minutes.
        if (userCount != -1 && System.currentTimeMillis() < expiresStamp) {
            return userCount;
        }
        this.userCount = manager.retrieveListCount(
                manager.getUsernameField(),
                MessageFormat.format(manager.getSearchFilter(), "*")
        );
        this.expiresStamp = System.currentTimeMillis() + JiveConstants.MINUTE *5;
        return this.userCount;
    }

    @Override
    public Collection getUsernames() {
        return manager.retrieveList(
                manager.getUsernameField(),
                MessageFormat.format(manager.getSearchFilter(), "*"),
                -1,
                -1,
                null,
                true
        );
    }
    
    @Override
    public Collection getUsers() {
        return getUsers(-1, -1);
    }

    @Override
    public Collection getUsers(int startIndex, int numResults) {
        List userlist = manager.retrieveList(
                manager.getUsernameField(),
                MessageFormat.format(manager.getSearchFilter(), "*"),
                startIndex,
                numResults,
                manager.getUsernameSuffix(),
                true
        );
        return new UserCollection(userlist.toArray(new String[userlist.size()]));
    }

    @Override
    public void setName(String username, String name) throws UserNotFoundException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void setEmail(String username, String email) throws UserNotFoundException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void setCreationDate(String username, Date creationDate) throws UserNotFoundException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void setModificationDate(String username, Date modificationDate) throws UserNotFoundException {
        throw new UnsupportedOperationException();
    }

    @Override
    public Set getSearchFields() throws UnsupportedOperationException {
        return Collections.unmodifiableSet(searchFields.keySet());
    }

    public void setSearchFields(String fieldList) {
        this.searchFields = new LinkedHashMap<>();
        // If the value isn't present, default to to username, name, and email.
        if (fieldList == null) {
            searchFields.put("Username", manager.getUsernameField());
            searchFields.put("Name", manager.getNameField());
            searchFields.put("Email", manager.getEmailField());
        }
        else {
            try {
                for (StringTokenizer i=new StringTokenizer(fieldList, ","); i.hasMoreTokens(); ) {
                    String[] field = i.nextToken().split("/");
                    searchFields.put(field[0], field[1]);
                }
            }
            catch (Exception e) {
                Log.error("Error parsing LDAP search fields: " + fieldList, e);
            }
        }
        JiveGlobals.setProperty("ldap.searchFields", fieldList);
    }

    @Override
    public Collection findUsers(Set fields, String query)
            throws UnsupportedOperationException
    {
        return findUsers(fields, query, -1, -1);
    }

    @Override
    public Collection findUsers(Set fields, String query, int startIndex,
            int numResults) throws UnsupportedOperationException
    {
        if (fields.isEmpty() || query == null || "".equals(query)) {
            return Collections.emptyList();
        }
        
        query = LdapManager.sanitizeSearchFilter(query, true);
        
        // Make the query be a wildcard search by default. So, if the user searches for
        // "John", make the search be "John*" instead.
        if (!query.endsWith("*")) {
            query = query + "*";
        }

        if (!searchFields.keySet().containsAll(fields)) {
            throw new IllegalArgumentException("Search fields " + fields + " are not valid.");
        }
        StringBuilder filter = new StringBuilder();
        //Add the global search filter so only those users the directory administrator wants to include
        //are returned from the directory
        filter.append("(&(");
        filter.append(MessageFormat.format(manager.getSearchFilter(),"*"));
        filter.append(')');
        if (fields.size() > 1) {
            filter.append("(|");
        }
        for (String field:fields) {
            String attribute = searchFields.get(field);
            filter.append('(').append(attribute).append('=')
                .append( query ).append(")");
        }
        if (fields.size() > 1) {
            filter.append(')');
        }
        filter.append(')');
        List userlist = manager.retrieveList(
                manager.getUsernameField(),
                filter.toString(),
                startIndex,
                numResults,
                manager.getUsernameSuffix(),
                true
        );
        return new UserCollection(userlist.toArray(new String[userlist.size()]));
    }

    @Override
    public boolean isReadOnly() {
        return true;
    }

    @Override
    public boolean isNameRequired() {
        return false;
    }

    @Override
    public boolean isEmailRequired() {
        return false;
    }

    /**
     * Parses dates/time stamps stored in LDAP. Some possible values:
     *
     * 
    *
  • 20020228150820
  • *
  • 20030228150820Z
  • *
  • 20050228150820.12
  • *
  • 20060711011740.0Z
  • *
* * @param dateText the date string. * @return the Date. */ private static Date parseLDAPDate(String dateText) { // If the date ends with a "Z", that means that it's in the UTC time zone. Otherwise, // Use the default time zone. boolean useUTC = false; if (dateText.endsWith("Z")) { useUTC = true; } Date date = new Date(); try { if (useUTC) { ldapDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); } else { ldapDateFormat.setTimeZone(TimeZone.getDefault()); } date = ldapDateFormat.parse(dateText); } catch (Exception e) { Log.error(e.getMessage(), e); } return date; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy