Commit 6f9f814c authored by Guus der Kinderen's avatar Guus der Kinderen

Merge pull request #548 from guusdk/OF-1092

OF-1092: Pluggable SASL mechanism
parents 4a1ac2bd 9017ee09
package org.jivesoftware.openfire.sasl;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.session.LocalClientSession;
import org.jivesoftware.openfire.session.LocalSession;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
/**
* Implementation of the SASL ANONYMOUS mechanism.
*
* @author Guus der Kinderen, guus@goodbytes.nl
* @see <a href="http://tools.ietf.org/html/rfc4505">RFC 4505</a>
* @see <a href="http://xmpp.org/extensions/xep-0175.html">XEP 0175</a>
*/
public class AnonymousSaslServer implements SaslServer
{
public static final String NAME = "ANONYMOUS";
private boolean complete = false;
private LocalSession session;
public AnonymousSaslServer( LocalSession session )
{
this.session = session;
}
@Override
public String getMechanismName()
{
return NAME;
}
@Override
public byte[] evaluateResponse( byte[] response ) throws SaslException
{
if ( isComplete() )
{
throw new IllegalStateException( "Authentication exchange already completed." );
}
complete = true;
// Verify server-wide policy.
if ( !XMPPServer.getInstance().getIQAuthHandler().isAnonymousAllowed() )
{
throw new SaslException( "Authentication failed" );
}
// Verify that client can connect from his IP address.
final boolean forbidAccess = !LocalClientSession.isAllowedAnonymous( session.getConnection() );
if ( forbidAccess )
{
throw new SaslException( "Authentication failed" );
}
// Just accept the authentication :)
return null;
}
@Override
public boolean isComplete()
{
return complete;
}
@Override
public String getAuthorizationID()
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
return null; // Anonymous!
}
@Override
public byte[] unwrap( byte[] incoming, int offset, int len ) throws SaslException
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
throw new IllegalStateException( "SASL Mechanism '" + getMechanismName() + " does not support integrity nor privacy." );
}
@Override
public byte[] wrap( byte[] outgoing, int offset, int len ) throws SaslException
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
throw new IllegalStateException( "SASL Mechanism '" + getMechanismName() + " does not support integrity nor privacy." );
}
@Override
public Object getNegotiatedProperty( String propName )
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
if ( propName.equals( Sasl.QOP ) )
{
return "auth";
}
else
{
return null;
}
}
@Override
public void dispose() throws SaslException
{
complete = false;
session = null;
}
}
package org.jivesoftware.openfire.sasl;
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.auth.AuthorizationManager;
import org.jivesoftware.openfire.session.LocalClientSession;
import org.jivesoftware.util.CertificateManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
/**
* Implementation of the SASL EXTERNAL mechanism with PKIX to be used for client-to-server connections.
*
* @author Guus der Kinderen, guus@goodbytes.nl
* @see <a href="http://tools.ietf.org/html/rfc6125">RFC 6125</a>
* @see <a href="http://xmpp.org/extensions/xep-0178.html">XEP 0178</a>
*/
public class ExternalClientSaslServer implements SaslServer
{
public static final Logger Log = LoggerFactory.getLogger( ExternalClientSaslServer.class );
public static final String NAME = "EXTERNAL";
private boolean complete = false;
private String authorizationID = null;
private LocalClientSession session;
public ExternalClientSaslServer( LocalClientSession session ) throws SaslException
{
this.session = session;
}
@Override
public String getMechanismName()
{
return NAME;
}
@Override
public byte[] evaluateResponse( byte[] response ) throws SaslException
{
if ( isComplete() )
{
throw new IllegalStateException( "Authentication exchange already completed." );
}
// There will be no further steps. Either authentication succeeds or fails, but in any case, we're done.
complete = true;
final Connection connection = session.getConnection();
if ( connection.getPeerCertificates().length < 1 )
{
throw new SaslException( "No peer certificates." );
}
final KeyStore keyStore = connection.getConfiguration().getIdentityStore().getStore();
final KeyStore trustStore = connection.getConfiguration().getTrustStore().getStore();
final X509Certificate trusted = CertificateManager.getEndEntityCertificate( connection.getPeerCertificates(), keyStore, trustStore );
if ( trusted == null )
{
throw new SaslException( "Certificate chain of peer is not trusted." );
}
// Process client identities / principals.
final ArrayList<String> principals = new ArrayList<>();
principals.addAll( CertificateManager.getClientIdentities( trusted ) );
String principal;
switch ( principals.size() )
{
case 0:
principal = "";
break;
default:
Log.debug( "More than one principal found, using the first one." );
// intended fall-through;
case 1:
principal = principals.get( 0 );
break;
}
// Process requested user name.
String username;
if ( response != null && response.length > 0 )
{
username = new String( response, StandardCharsets.UTF_8 );
}
else
{
username = null;
}
if ( username == null || username.length() == 0 )
{
// No username was provided, according to XEP-0178 we need to:
// * attempt to get it from the cert first
// * have the server assign one
// There shouldn't be more than a few principals in here. One ideally. We set principal to the first one in
// the list to have a sane default. If this list is empty, then the cert had no identity at all, which will
// cause an authorization failure.
for ( String princ : principals )
{
final String mappedUsername = AuthorizationManager.map( princ );
if ( !mappedUsername.equals( princ ) )
{
username = mappedUsername;
principal = princ;
break;
}
}
if ( username == null || username.length() == 0 )
{
// Still no username. Punt.
username = principal;
}
Log.debug( "No username requested, using: {}", username );
}
// Its possible that either/both username and principal are null here. The providers should not allow a null authorization
if ( AuthorizationManager.authorize( username, principal ) )
{
Log.debug( "Principal {} authorized to username {}", principal, username );
authorizationID = username;
return null; // Success!
}
throw new SaslException();
}
@Override
public boolean isComplete()
{
return complete;
}
@Override
public String getAuthorizationID()
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
return authorizationID;
}
@Override
public byte[] unwrap( byte[] incoming, int offset, int len ) throws SaslException
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
throw new IllegalStateException( "SASL Mechanism '" + getMechanismName() + " does not support integrity nor privacy." );
}
@Override
public byte[] wrap( byte[] outgoing, int offset, int len ) throws SaslException
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
throw new IllegalStateException( "SASL Mechanism '" + getMechanismName() + " does not support integrity nor privacy." );
}
@Override
public Object getNegotiatedProperty( String propName )
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
if ( propName.equals( Sasl.QOP ) )
{
return "auth";
}
else
{
return null;
}
}
@Override
public void dispose() throws SaslException
{
complete = false;
authorizationID = null;
session = null;
}
}
package org.jivesoftware.openfire.sasl;
import org.jivesoftware.openfire.session.LocalIncomingServerSession;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
import java.nio.charset.StandardCharsets;
/**
* Implementation of the SASL EXTERNAL mechanism with PKIX to be used for server-to-server connections.
*
* @author Guus der Kinderen, guus@goodbytes.nl
* @see <a href="http://tools.ietf.org/html/rfc6125">RFC 6125</a>
* @see <a href="http://xmpp.org/extensions/xep-0178.html">XEP 0178</a>
*/
public class ExternalServerSaslServer implements SaslServer
{
public static final String NAME = "EXTERNAL";
private boolean complete = false;
private String authorizationID = null;
private LocalIncomingServerSession session;
public ExternalServerSaslServer( LocalIncomingServerSession session ) throws SaslException
{
this.session = session;
}
@Override
public String getMechanismName()
{
return NAME;
}
@Override
public byte[] evaluateResponse( byte[] response ) throws SaslException
{
if ( isComplete() )
{
throw new IllegalStateException( "Authentication exchange already completed." );
}
if ( response == null || response.length == 0 )
{
// No hostname was provided so send a challenge to get it
return new byte[ 0 ];
}
complete = true;
final String requestedId = new String( response, StandardCharsets.UTF_8 );
final String defaultIdentity = session.getDefaultIdentity();
if ( !requestedId.equals( defaultIdentity ) )
{
throw new SaslException( "From '" + requestedId + "' does not equal authzid '" + defaultIdentity + "'" );
}
authorizationID = requestedId;
return null; // Success!
}
@Override
public boolean isComplete()
{
return complete;
}
@Override
public String getAuthorizationID()
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
return authorizationID;
}
@Override
public byte[] unwrap( byte[] incoming, int offset, int len ) throws SaslException
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
throw new IllegalStateException( "SASL Mechanism '" + getMechanismName() + " does not support integrity nor privacy." );
}
@Override
public byte[] wrap( byte[] outgoing, int offset, int len ) throws SaslException
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
throw new IllegalStateException( "SASL Mechanism '" + getMechanismName() + " does not support integrity nor privacy." );
}
@Override
public Object getNegotiatedProperty( String propName )
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
if ( propName.equals( Sasl.QOP ) )
{
return "auth";
}
else
{
return null;
}
}
@Override
public void dispose() throws SaslException
{
complete = false;
authorizationID = null;
session = null;
}
}
package org.jivesoftware.openfire.sasl;
/**
* XMPP specified SASL errors.
*
* @author Guus der Kinderen, guus@goodbytes.nl
* @see <a href="http://tools.ietf.org/html/rfc6120#section-6.5">RFC 6120 section 6.5</a>
*/
public enum Failure
{
ABORTED( "aborted" ),
ACCOUNT_DISABLED( "account-disabled" ),
CREDENTIALS_EXPIRED( "credentials-expired" ),
ENCRYPTION_REQUIRED( "encryption-required" ),
INCORRECT_ENCODING( "incorrect-encoding" ),
INVALID_AUTHZID( "invalid-authzid" ),
INVALID_MECHANISM( "invalid-mechanism" ),
MALFORMED_REQUEST( "malformed-request" ),
MECHANISM_TOO_WEAK( "mechanism-too-weak" ),
NOT_AUTHORIZED( "not-authorized" ),
TEMPORARY_AUTH_FAILURE( "temporary-auth-failure" );
private String name = null;
Failure( String name )
{
this.name = name;
}
@Override
public String toString()
{
return name;
}
}
package org.jivesoftware.openfire.sasl;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.StringUtils;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
import java.nio.charset.StandardCharsets;
import java.util.StringTokenizer;
/**
* Implementation of a proprietary Jive Software SASL mechanism that is based on a shared secret. Successful
* authentication will result in an anonymous authorization.
*
* @author Guus der Kinderen, guus@goodbytes.nl
*/
public class JiveSharedSecretSaslServer implements SaslServer
{
public static final String NAME = "JIVE-SHAREDSECRET";
private boolean complete = false;
@Override
public String getMechanismName()
{
return NAME;
}
@Override
public byte[] evaluateResponse( byte[] response ) throws SaslException
{
if ( isComplete() )
{
throw new IllegalStateException( "Authentication exchange already completed." );
}
if ( response == null || response.length == 0 )
{
// No info was provided so send a challenge to get it.
return new byte[ 0 ];
}
complete = true;
// Parse data and obtain username & password.
final StringTokenizer tokens = new StringTokenizer( new String( response, StandardCharsets.UTF_8 ), "\0" );
tokens.nextToken();
final String secretDigest = tokens.nextToken();
if ( authenticateSharedSecret( secretDigest ) )
{
return null; // Success!
}
else
{
// Otherwise, authentication failed.
throw new SaslException( "Authentication failed" );
}
}
@Override
public boolean isComplete()
{
return complete;
}
@Override
public String getAuthorizationID()
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
return null; // Anonymous!
}
@Override
public byte[] unwrap( byte[] incoming, int offset, int len ) throws SaslException
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
throw new IllegalStateException( "SASL Mechanism '" + getMechanismName() + " does not support integrity nor privacy." );
}
@Override
public byte[] wrap( byte[] outgoing, int offset, int len ) throws SaslException
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
throw new IllegalStateException( "SASL Mechanism '" + getMechanismName() + " does not support integrity nor privacy." );
}
@Override
public Object getNegotiatedProperty( String propName )
{
if ( !isComplete() )
{
throw new IllegalStateException( "Authentication exchange not completed." );
}
if ( propName.equals( Sasl.QOP ) )
{
return "auth";
}
else
{
return null;
}
}
@Override
public void dispose() throws SaslException
{
complete = false;
}
/**
* Returns true if the supplied digest matches the shared secret value. The digest must be an MD5 hash of the secret
* key, encoded as hex. This value is supplied by clients attempting shared secret authentication.
*
* @param digest the MD5 hash of the secret key, encoded as hex.
* @return true if authentication succeeds.
*/
public static boolean authenticateSharedSecret( String digest )
{
if ( !isSharedSecretAllowed() )
{
return false;
}
return StringUtils.hash( getSharedSecret() ).equals( digest );
}
/**
* Returns true if shared secret authentication is enabled. Shared secret authentication creates an anonymous
* session, but requires that the authenticating entity know a shared secret key. The client sends a digest of the
* secret key, which is compared against a digest of the local shared key.
*
* @return true if shared secret authentication is enabled.
*/
public static boolean isSharedSecretAllowed()
{
return JiveGlobals.getBooleanProperty( "xmpp.auth.sharedSecretEnabled" );
}
/**
* Returns the shared secret value, or <tt>null</tt> if shared secret authentication is disabled. If this is the
* first time the shared secret value has been requested (and shared secret auth is enabled), the key will be
* randomly generated and stored in the property <tt>xmpp.auth.sharedSecret</tt>.
*
* @return the shared secret value.
*/
public static String getSharedSecret()
{
if ( !isSharedSecretAllowed() )
{
return null;
}
String sharedSecret = JiveGlobals.getProperty( "xmpp.auth.sharedSecret" );
if ( sharedSecret == null )
{
sharedSecret = StringUtils.randomString( 8 );
JiveGlobals.setProperty( "xmpp.auth.sharedSecret", sharedSecret );
}
return sharedSecret;
}
/**
* Sets whether shared secret authentication is enabled. Shared secret authentication creates an anonymous session,
* but requires that the authenticating entity know a shared secret key. The client sends a digest of the secret
* key, which is compared against a digest of the local shared key.
*
* @param sharedSecretAllowed true if shared secret authentication should be enabled.
*/
public static void setSharedSecretAllowed( boolean sharedSecretAllowed )
{
JiveGlobals.setProperty( "xmpp.auth.sharedSecretEnabled", sharedSecretAllowed ? "true" : "false" );
}
}
package org.jivesoftware.openfire.sasl;
import javax.security.sasl.SaslException;
/**
* A SaslException with XMPP 'failure' context.
*
* @author Guus der Kinderen, guus@goodbytes.nl
*/
public class SaslFailureException extends SaslException
{
private final Failure failure;
public SaslFailureException( Failure failure, String message )
{
super( message );
this.failure = failure;
}
public SaslFailureException( Failure failure )
{
this.failure = failure;
}
public SaslFailureException( String detail, Failure failure )
{
super( detail );
this.failure = failure;
}
public SaslFailureException( String detail, Throwable ex, Failure failure )
{
super( detail, ex );
this.failure = failure;
}
public Failure getFailure()
{
return failure;
}
}
......@@ -20,7 +20,16 @@
package org.jivesoftware.openfire.sasl;
import org.jivesoftware.openfire.session.LocalClientSession;
import org.jivesoftware.openfire.session.LocalIncomingServerSession;
import org.jivesoftware.openfire.session.LocalSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.security.auth.callback.CallbackHandler;
import javax.security.sasl.Sasl;
......@@ -34,72 +43,125 @@ import javax.security.sasl.SaslServerFactory;
* @author Jay Kline
*/
public class SaslServerFactoryImpl implements SaslServerFactory {
public class SaslServerFactoryImpl implements SaslServerFactory
{
private final static Logger Log = LoggerFactory.getLogger( SaslServerFactoryImpl.class );
private static final String myMechs[] = { "PLAIN", "SCRAM-SHA-1" };
private static final int PLAIN = 0;
private static final int SCRAM_SHA_1 = 1;
/**
* All mechanisms provided by this factory.
*/
private final Set<Mechanism> allMechanisms;
public SaslServerFactoryImpl() {
public SaslServerFactoryImpl()
{
allMechanisms = new HashSet<>();
allMechanisms.add( new Mechanism( "PLAIN", true, true ) );
allMechanisms.add( new Mechanism( "SCRAM_SHA_1", false, false ) );
allMechanisms.add( new Mechanism( "JIVE-SHAREDSECRET", true, false ) );
allMechanisms.add( new Mechanism( "EXTERNAL", false, false ) );
}
/**
* Creates a <code>SaslServer</code> implementing a supported mechanism using the parameters supplied.
*
* @param mechanism The non-null IANA-registered named of a SASL mechanism.
* @param protocol The non-null string name of the protocol for which the authentication is being performed (e.g., "ldap").
* @param serverName The non-null fully qualified host name of the server to authenticate to.
* @param props The possibly null set of properties used to select the SASL mechanism and to configure the authentication exchange of the selected mechanism.
* @param cbh The possibly null callback handler to used by the SASL mechanisms to get further information from the application/library to complete the authentication.
* @return A possibly null SaslServer created using the parameters supplied. If null, this factory cannot produce a SaslServer using the parameters supplied.
* @throws SaslException If cannot create a SaslServer because of an error.
*/
@Override
public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props, CallbackHandler cbh) throws SaslException
{
if ( !Arrays.asList( getMechanismNames( props )).contains( mechanism ) )
{
Log.debug( "This implementation is unable to create a SaslServer instance for the {} mechanism using the provided properties.", mechanism );
return null;
}
switch ( mechanism.toUpperCase() )
{
case "PLAIN":
if ( cbh != null )
{
Log.debug( "Unable to instantiate {} SaslServer: A callbackHandler with support for Password, Name, and AuthorizeCallback required.", mechanism );
return null;
}
return new SaslServerPlainImpl( protocol, serverName, props, cbh );
case "SCRAM_SHA_1":
return new ScramSha1SaslServer();
case "ANONYMOUS":
if ( !props.containsKey( LocalSession.class.getCanonicalName() ) )
{
Log.debug( "Unable to instantiate {} SaslServer: Provided properties do not contain a LocalSession instance.", mechanism );
return null;
}
else
{
final LocalSession session = (LocalSession) props.get( LocalSession.class.getCanonicalName() );
return new AnonymousSaslServer( session );
}
case "EXTERNAL":
if ( !props.containsKey( LocalSession.class.getCanonicalName() ) )
{
Log.debug( "Unable to instantiate {} SaslServer: Provided properties do not contain a LocalSession instance.", mechanism );
return null;
}
else
{
final Object session = props.get( LocalSession.class.getCanonicalName() );
if ( session instanceof LocalClientSession )
{
return new ExternalClientSaslServer( (LocalClientSession) session );
}
if ( session instanceof LocalIncomingServerSession )
{
return new ExternalServerSaslServer( (LocalIncomingServerSession) session );
}
Log.debug( "Unable to instantiate {} Sasl Server: Provided properties contains neither LocalClientSession nor LocalIncomingServerSession instance.", mechanism );
return null;
}
case JiveSharedSecretSaslServer.NAME:
return new JiveSharedSecretSaslServer();
default:
throw new IllegalStateException(); // Fail fast - this should not be possible, as the first check in this method already verifies wether the mechanism is supported.
}
}
@Override
public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props, CallbackHandler cbh) throws SaslException {
if (mechanism.equals(myMechs[PLAIN]) && checkPolicy(props)) {
if (cbh == null) {
throw new SaslException("CallbackHandler with support for Password, Name, and AuthorizeCallback required");
public String[] getMechanismNames( Map<String, ?> props )
{
final Set<String> result = new HashSet<>();
for ( final Mechanism mechanism : allMechanisms )
{
if ( mechanism.allowsAnonymous && props.containsKey( Sasl.POLICY_NOANONYMOUS ) && Boolean.parseBoolean( (String) props.get( Sasl.POLICY_NOANONYMOUS ) ) )
{
// Do not include a mechanism that allows anonymous authentication when the 'no anonymous' policy is set.
continue;
}
return new SaslServerPlainImpl(protocol, serverName, props, cbh);
}
else if (mechanism.equals(myMechs[SCRAM_SHA_1])) {
if (cbh == null) {
throw new SaslException("CallbackHandler with support for AuthorizeCallback required");
}
return new ScramSha1SaslServer();
if ( mechanism.isPlaintext && props.containsKey( Sasl.POLICY_NOPLAINTEXT ) && Boolean.parseBoolean( (String) props.get( Sasl.POLICY_NOPLAINTEXT ) ) )
{
// Do not include a mechanism that is susceptible to simple plain passive attacks when the 'no plaintext' policy is set.
continue;
}
// Mechanism passed all filters. It should be part of the result.
result.add( mechanism.name );
}
return null;
return result.toArray( new String[ result.size() ] );
}
/**
* Requires supported mechanisms to allow anonymous logins
*
* @param props The security properties to check
* @return true if the policy allows anonymous logins
*/
private boolean checkPolicy(Map<String, ?> props) {
boolean result = true;
if (props != null) {
String policy = (String) props.get(Sasl.POLICY_NOANONYMOUS);
if (Boolean.parseBoolean(policy)) {
result = false;
}
}
return result;
}
/**
* Returns an array of names of mechanisms that match the specified mechanism selection policies.
* @param props The possibly null set of properties used to specify the security policy of the SASL mechanisms.
* @return A non-null array containing a IANA-registered SASL mechanism names.
*/
private static class Mechanism
{
final String name;
final boolean allowsAnonymous;
final boolean isPlaintext;
@Override
public String[] getMechanismNames(Map<String, ?> props) {
if (checkPolicy(props)) {
return myMechs;
}
return new String [] { };
private Mechanism( String name, boolean allowsAnonymous, boolean isPlaintext )
{
this.name = name;
this.allowsAnonymous = allowsAnonymous;
this.isPlaintext = isPlaintext;
}
}
}
......@@ -55,6 +55,7 @@ import org.jivesoftware.openfire.fastpath.util.TaskEngine;
import org.jivesoftware.openfire.fastpath.util.WorkgroupUtils;
import org.jivesoftware.openfire.group.Group;
import org.jivesoftware.openfire.net.SASLAuthentication;
import org.jivesoftware.openfire.sasl.JiveSharedSecretSaslServer;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.JiveGlobals;
......@@ -213,8 +214,8 @@ public class WorkgroupManager implements Component {
// We use a custom SASL mechanism so that web-based customer chats can login without
// a username or password. However, a shared secret key is still required so that
// anonymous login doesn't have to be enabled for the whole server.
if (!SASLAuthentication.isSharedSecretAllowed()) {
SASLAuthentication.setSharedSecretAllowed(true);
if (!JiveSharedSecretSaslServer.isSharedSecretAllowed()) {
JiveSharedSecretSaslServer.setSharedSecretAllowed( true );
}
// If the database was just created then create the "demo" user and "demo" workgroup
......
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