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;
}
}