Commit a5ae728b authored by Greg Thomas's avatar Greg Thomas

Ensure that LDAP configuration options are appropriately displayed

parent 23fa6f11
/* /*
* Copyright (C) 2004-2008 Jive Software. All rights reserved. * Copyright (C) 2004-2008 Jive Software. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jivesoftware.openfire.auth; package org.jivesoftware.openfire.auth;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Map; import java.util.Map;
import org.jivesoftware.openfire.lockout.LockOutManager; import org.jivesoftware.openfire.lockout.LockOutManager;
import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.Blowfish; import org.jivesoftware.util.Blowfish;
import org.jivesoftware.util.ClassUtils; import org.jivesoftware.util.ClassUtils;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.PropertyEventDispatcher; import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener; import org.jivesoftware.util.PropertyEventListener;
import org.jivesoftware.util.StringUtils; import org.jivesoftware.util.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* Pluggable authentication service. Users of Openfire that wish to change the AuthProvider * Pluggable authentication service. Users of Openfire that wish to change the AuthProvider
* implementation used to authenticate users can set the <code>AuthProvider.className</code> * implementation used to authenticate users can set the <code>AuthProvider.className</code>
* system property. For example, if you have configured Openfire to use LDAP for user information, * system property. For example, if you have configured Openfire to use LDAP for user information,
* you'd want to send a custom implementation of AuthFactory to make LDAP auth queries. * you'd want to send a custom implementation of AuthFactory to make LDAP auth queries.
* After changing the <code>AuthProvider.className</code> system property, you must restart your * After changing the <code>AuthProvider.className</code> system property, you must restart your
* application server. * application server.
* *
* @author Matt Tucker * @author Matt Tucker
*/ */
public class AuthFactory { public class AuthFactory {
private static final Logger Log = LoggerFactory.getLogger(AuthFactory.class); private static final Logger Log = LoggerFactory.getLogger(AuthFactory.class);
private static AuthProvider authProvider = null; private static AuthProvider authProvider = null;
private static MessageDigest digest; private static MessageDigest digest;
private static final Object DIGEST_LOCK = new Object(); private static final Object DIGEST_LOCK = new Object();
private static Blowfish cipher = null; private static Blowfish cipher = null;
static { static {
// Create a message digest instance. // Create a message digest instance.
try { try {
digest = MessageDigest.getInstance("SHA"); digest = MessageDigest.getInstance("SHA");
} }
catch (NoSuchAlgorithmException e) { catch (NoSuchAlgorithmException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e); Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
} }
// Load an auth provider. // Load an auth provider.
initProvider(); initProvider();
// Detect when a new auth provider class is set // Detect when a new auth provider class is set
PropertyEventListener propListener = new PropertyEventListener() { PropertyEventListener propListener = new PropertyEventListener() {
@Override @Override
public void propertySet(String property, Map params) { public void propertySet(String property, Map params) {
if ("provider.auth.className".equals(property)) { if ("provider.auth.className".equals(property)) {
initProvider(); initProvider();
} }
} }
@Override @Override
public void propertyDeleted(String property, Map params) { public void propertyDeleted(String property, Map params) {
//Ignore //Ignore
} }
@Override @Override
public void xmlPropertySet(String property, Map params) { public void xmlPropertySet(String property, Map params) {
//Ignore //Ignore
} }
@Override @Override
public void xmlPropertyDeleted(String property, Map params) { public void xmlPropertyDeleted(String property, Map params) {
//Ignore //Ignore
} }
}; };
PropertyEventDispatcher.addListener(propListener); PropertyEventDispatcher.addListener(propListener);
} }
private static void initProvider() { private static void initProvider() {
// Convert XML based provider setup to Database based // Convert XML based provider setup to Database based
JiveGlobals.migrateProperty("provider.auth.className"); JiveGlobals.migrateProperty("provider.auth.className");
String className = JiveGlobals.getProperty("provider.auth.className", String className = JiveGlobals.getProperty("provider.auth.className",
"org.jivesoftware.openfire.auth.DefaultAuthProvider"); "org.jivesoftware.openfire.auth.DefaultAuthProvider");
// Check if we need to reset the auth provider class // Check if we need to reset the auth provider class
if (authProvider == null || !className.equals(authProvider.getClass().getName())) { if (authProvider == null || !className.equals(authProvider.getClass().getName())) {
try { try {
Class c = ClassUtils.forName(className); Class c = ClassUtils.forName(className);
authProvider = (AuthProvider)c.newInstance(); authProvider = (AuthProvider)c.newInstance();
} }
catch (Exception e) { catch (Exception e) {
Log.error("Error loading auth provider: " + className, e); Log.error("Error loading auth provider: " + className, e);
authProvider = new DefaultAuthProvider(); authProvider = new DefaultAuthProvider();
} }
} }
} }
/** /**
* Returns the currently-installed AuthProvider. <b>Warning:</b> in virtually all * Returns the currently-installed AuthProvider. <b>Warning:</b> in virtually all
* cases the auth provider should not be used directly. Instead, the appropriate * cases the auth provider should not be used directly. Instead, the appropriate
* methods in AuthFactory should be called. Direct access to the auth provider is * methods in AuthFactory should be called. Direct access to the auth provider is
* only provided for special-case logic. * only provided for special-case logic.
* *
* @return the current UserProvider. * @return the current UserProvider.
* @deprecated Prefer using the corresponding factory method, rather than * @deprecated Prefer using the corresponding factory method, rather than
* invoking methods on the provider directly * invoking methods on the provider directly
*/ */
public static AuthProvider getAuthProvider() { public static AuthProvider getAuthProvider() {
return authProvider; return authProvider;
} }
/** /**
* Returns whether the currently-installed AuthProvider is instance of a specific class. * Returns whether the currently-installed AuthProvider is instance of a specific class.
* @param c the class to compare with * @param c the class to compare with
* @return true - if the currently-installed AuthProvider is instance of c, false otherwise. * @return true - if the currently-installed AuthProvider is instance of c, false otherwise.
*/ */
public static boolean isProviderInstanceOf(Class<?> c) { public static boolean isProviderInstanceOf(Class<?> c) {
return c.isInstance(authProvider); return c.isInstance(authProvider);
} }
/** /**
* Returns true if the currently installed {@link AuthProvider} supports password * Indicates if the currently-installed AuthProvider is the HybridAuthProvider supporting a specific class.
* retrieval. Certain implementation utilize password hashes and other authentication *
* mechanisms that do not require the original password. * @param clazz the class to check
* * @return {@code true} if the currently-installed AuthProvider is a HybridAuthProvider that supports an instance of clazz, otherwise {@code false}.
* @return true if plain password retrieval is supported. */
*/ public static boolean isProviderHybridInstanceOf(Class<? extends AuthProvider> clazz) {
public static boolean supportsPasswordRetrieval() { return authProvider instanceof HybridAuthProvider &&
return authProvider.supportsPasswordRetrieval(); ((HybridAuthProvider) authProvider).isProvider(clazz);
} }
/** /**
* Returns the user's password. This method will throw an UnsupportedOperationException * Returns true if the currently installed {@link AuthProvider} supports password
* if this operation is not supported by the backend user store. * retrieval. Certain implementation utilize password hashes and other authentication
* * mechanisms that do not require the original password.
* @param username the username of the user. *
* @return the user's password. * @return true if plain password retrieval is supported.
* @throws UserNotFoundException if the given user could not be found. */
* @throws UnsupportedOperationException if the provider does not public static boolean supportsPasswordRetrieval() {
* support the operation (this is an optional operation). return authProvider.supportsPasswordRetrieval();
*/ }
public static String getPassword(String username) throws UserNotFoundException,
UnsupportedOperationException { /**
return authProvider.getPassword(username.toLowerCase()); * Returns the user's password. This method will throw an UnsupportedOperationException
} * if this operation is not supported by the backend user store.
*
/** * @param username the username of the user.
* Sets the users's password. This method should throw an UnsupportedOperationException * @return the user's password.
* if this operation is not supported by the backend user store. * @throws UserNotFoundException if the given user could not be found.
* * @throws UnsupportedOperationException if the provider does not
* @param username the username of the user. * support the operation (this is an optional operation).
* @param password the new plaintext password for the user. */
* @throws UserNotFoundException if the given user could not be loaded. public static String getPassword(String username) throws UserNotFoundException,
* @throws UnsupportedOperationException if the provider does not UnsupportedOperationException {
* support the operation (this is an optional operation). return authProvider.getPassword(username.toLowerCase());
*/ }
public static void setPassword(String username, String password) throws UserNotFoundException,
UnsupportedOperationException, ConnectionException, InternalUnauthenticatedException { /**
authProvider.setPassword(username, password); * Sets the users's password. This method should throw an UnsupportedOperationException
} * if this operation is not supported by the backend user store.
*
/** * @param username the username of the user.
* Authenticates a user with a username and plain text password and returns and * @param password the new plaintext password for the user.
* AuthToken. If the username and password do not match the record of * @throws UserNotFoundException if the given user could not be loaded.
* any user in the system, this method throws an UnauthorizedException. * @throws UnsupportedOperationException if the provider does not
* * support the operation (this is an optional operation).
* @param username the username. */
* @param password the password. public static void setPassword(String username, String password) throws UserNotFoundException,
* @return an AuthToken token if the username and password are correct. UnsupportedOperationException, ConnectionException, InternalUnauthenticatedException {
* @throws UnauthorizedException if the username and password do not match any existing user authProvider.setPassword(username, password);
* or the account is locked out. }
*/
public static AuthToken authenticate(String username, String password) /**
throws UnauthorizedException, ConnectionException, InternalUnauthenticatedException { * Authenticates a user with a username and plain text password and returns and
if (LockOutManager.getInstance().isAccountDisabled(username)) { * AuthToken. If the username and password do not match the record of
LockOutManager.getInstance().recordFailedLogin(username); * any user in the system, this method throws an UnauthorizedException.
throw new UnauthorizedException(); *
} * @param username the username.
authProvider.authenticate(username, password); * @param password the password.
return new AuthToken(username); * @return an AuthToken token if the username and password are correct.
} * @throws UnauthorizedException if the username and password do not match any existing user
* or the account is locked out.
/** */
* Returns a digest given a token and password, according to JEP-0078. public static AuthToken authenticate(String username, String password)
* throws UnauthorizedException, ConnectionException, InternalUnauthenticatedException {
* @param token the token used in the digest. if (LockOutManager.getInstance().isAccountDisabled(username)) {
* @param password the plain-text password to be digested. LockOutManager.getInstance().recordFailedLogin(username);
* @return the digested result as a hex string. throw new UnauthorizedException();
*/ }
public static String createDigest(String token, String password) { authProvider.authenticate(username, password);
synchronized (DIGEST_LOCK) { return new AuthToken(username);
digest.update(token.getBytes()); }
return StringUtils.encodeHex(digest.digest(password.getBytes()));
} /**
} * Returns a digest given a token and password, according to JEP-0078.
*
/** * @param token the token used in the digest.
* Returns an encrypted version of the plain-text password. Encryption is performed * @param password the plain-text password to be digested.
* using the Blowfish algorithm. The encryption key is stored as the Jive property * @return the digested result as a hex string.
* "passwordKey". If the key is not present, it will be automatically generated. */
* public static String createDigest(String token, String password) {
* @param password the plain-text password. synchronized (DIGEST_LOCK) {
* @return the encrypted password. digest.update(token.getBytes());
* @throws UnsupportedOperationException if encryption/decryption is not possible; return StringUtils.encodeHex(digest.digest(password.getBytes()));
* for example, during setup mode. }
*/ }
public static String encryptPassword(String password) {
if (password == null) { /**
return null; * Returns an encrypted version of the plain-text password. Encryption is performed
} * using the Blowfish algorithm. The encryption key is stored as the Jive property
Blowfish cipher = getCipher(); * "passwordKey". If the key is not present, it will be automatically generated.
if (cipher == null) { *
throw new UnsupportedOperationException(); * @param password the plain-text password.
} * @return the encrypted password.
return cipher.encryptString(password); * @throws UnsupportedOperationException if encryption/decryption is not possible;
} * for example, during setup mode.
*/
/** public static String encryptPassword(String password) {
* Returns a decrypted version of the encrypted password. Encryption is performed if (password == null) {
* using the Blowfish algorithm. The encryption key is stored as the Jive property return null;
* "passwordKey". If the key is not present, it will be automatically generated. }
* Blowfish cipher = getCipher();
* @param encryptedPassword the encrypted password. if (cipher == null) {
* @return the encrypted password. throw new UnsupportedOperationException();
* @throws UnsupportedOperationException if encryption/decryption is not possible; }
* for example, during setup mode. return cipher.encryptString(password);
*/ }
public static String decryptPassword(String encryptedPassword) {
if (encryptedPassword == null) { /**
return null; * Returns a decrypted version of the encrypted password. Encryption is performed
} * using the Blowfish algorithm. The encryption key is stored as the Jive property
Blowfish cipher = getCipher(); * "passwordKey". If the key is not present, it will be automatically generated.
if (cipher == null) { *
throw new UnsupportedOperationException(); * @param encryptedPassword the encrypted password.
} * @return the encrypted password.
return cipher.decryptString(encryptedPassword); * @throws UnsupportedOperationException if encryption/decryption is not possible;
} * for example, during setup mode.
*/
/** public static String decryptPassword(String encryptedPassword) {
* Returns a Blowfish cipher that can be used for encrypting and decrypting passwords. if (encryptedPassword == null) {
* The encryption key is stored as the Jive property "passwordKey". If it's not present, return null;
* it will be automatically generated. }
* Blowfish cipher = getCipher();
* @return the Blowfish cipher, or <tt>null</tt> if Openfire is not able to create a Cipher; if (cipher == null) {
* for example, during setup mode. throw new UnsupportedOperationException();
*/ }
private static synchronized Blowfish getCipher() { return cipher.decryptString(encryptedPassword);
if (cipher != null) { }
return cipher;
} /**
// Get the password key, stored as a database property. Obviously, * Returns a Blowfish cipher that can be used for encrypting and decrypting passwords.
// protecting your database is critical for making the * The encryption key is stored as the Jive property "passwordKey". If it's not present,
// encryption fully secure. * it will be automatically generated.
String keyString; *
try { * @return the Blowfish cipher, or <tt>null</tt> if Openfire is not able to create a Cipher;
keyString = JiveGlobals.getProperty("passwordKey"); * for example, during setup mode.
if (keyString == null) { */
keyString = StringUtils.randomString(15); private static synchronized Blowfish getCipher() {
JiveGlobals.setProperty("passwordKey", keyString); if (cipher != null) {
// Check to make sure that setting the property worked. It won't work, return cipher;
// for example, when in setup mode. }
if (!keyString.equals(JiveGlobals.getProperty("passwordKey"))) { // Get the password key, stored as a database property. Obviously,
return null; // protecting your database is critical for making the
} // encryption fully secure.
} String keyString;
cipher = new Blowfish(keyString); try {
} keyString = JiveGlobals.getProperty("passwordKey");
catch (Exception e) { if (keyString == null) {
Log.error(e.getMessage(), e); keyString = StringUtils.randomString(15);
} JiveGlobals.setProperty("passwordKey", keyString);
return cipher; // Check to make sure that setting the property worked. It won't work,
} // for example, when in setup mode.
if (!keyString.equals(JiveGlobals.getProperty("passwordKey"))) {
public static boolean supportsScram() { return null;
// TODO Auto-generated method stub }
return authProvider.isScramSupported(); }
} cipher = new Blowfish(keyString);
}
public static String getSalt(String username) throws UnsupportedOperationException, UserNotFoundException { catch (Exception e) {
return authProvider.getSalt(username); Log.error(e.getMessage(), e);
} }
public static int getIterations(String username) throws UnsupportedOperationException, UserNotFoundException { return cipher;
return authProvider.getIterations(username); }
}
public static String getServerKey(String username) throws UnsupportedOperationException, UserNotFoundException { public static boolean supportsScram() {
return authProvider.getServerKey(username); // TODO Auto-generated method stub
} return authProvider.isScramSupported();
public static String getStoredKey(String username) throws UnsupportedOperationException, UserNotFoundException { }
return authProvider.getStoredKey(username);
} public static String getSalt(String username) throws UnsupportedOperationException, UserNotFoundException {
} return authProvider.getSalt(username);
\ No newline at end of file }
public static int getIterations(String username) throws UnsupportedOperationException, UserNotFoundException {
return authProvider.getIterations(username);
}
public static String getServerKey(String username) throws UnsupportedOperationException, UserNotFoundException {
return authProvider.getServerKey(username);
}
public static String getStoredKey(String username) throws UnsupportedOperationException, UserNotFoundException {
return authProvider.getStoredKey(username);
}
}
...@@ -249,4 +249,10 @@ public class HybridAuthProvider implements AuthProvider { ...@@ -249,4 +249,10 @@ public class HybridAuthProvider implements AuthProvider {
public String getStoredKey(String username) throws UnsupportedOperationException, UserNotFoundException { public String getStoredKey(String username) throws UnsupportedOperationException, UserNotFoundException {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
}
\ No newline at end of file boolean isProvider(final Class<? extends AuthProvider> clazz) {
return (primaryProvider != null && clazz.isAssignableFrom(primaryProvider.getClass()))
|| (secondaryProvider != null && clazz.isAssignableFrom(secondaryProvider.getClass()))
||(tertiaryProvider != null && clazz.isAssignableFrom(tertiaryProvider.getClass()));
}
}
<%@ page import="org.jivesoftware.util.JiveGlobals" %> <%@ page import="org.jivesoftware.util.JiveGlobals" %>
<%@ page import="org.jivesoftware.openfire.ldap.LdapManager" %> <%@ page import="org.jivesoftware.openfire.ldap.LdapManager" %>
<%@ page import="org.jivesoftware.openfire.auth.AuthFactory" %>
<%@ page import="org.jivesoftware.openfire.ldap.LdapAuthProvider" %>
<%-- <%--
- -
- Copyright (C) 2005-2008 Jive Software. All rights reserved. - Copyright (C) 2005-2008 Jive Software. All rights reserved.
...@@ -36,8 +38,8 @@ ...@@ -36,8 +38,8 @@
</head> </head>
<body> <body>
<% <%
boolean isLDAP = "org.jivesoftware.openfire.ldap.LdapAuthProvider".equals( boolean isLDAP = "org.jivesoftware.openfire.ldap.LdapAuthProvider".equals(JiveGlobals.getProperty("provider.auth.className"))
JiveGlobals.getProperty("provider.auth.className")); || AuthFactory.isProviderHybridInstanceOf(LdapAuthProvider.class);
%> %>
<p> <p>
<fmt:message key="profile-settings.info"/> <fmt:message key="profile-settings.info"/>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment