Commit 9017ee09 authored by Guus der Kinderen's avatar Guus der Kinderen

OF-1092: Refacotring SASL

A bit of rework of the SASL implementation. Pushed implementations to Java
Provider where this was not done before. When SASL fails, it is useful to be
able to add some context to the failure. To do this,
javax.security.sasl.SaslException is subclassed.
parent 5ec02acf
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 {
private static final String myMechs[] = { "PLAIN", "SCRAM-SHA-1" };
private static final int PLAIN = 0;
private static final int SCRAM_SHA_1 = 1;
public SaslServerFactoryImpl() {
}
public class SaslServerFactoryImpl implements SaslServerFactory
{
private final static Logger Log = LoggerFactory.getLogger( SaslServerFactoryImpl.class );
/**
* 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.
* All mechanisms provided by this factory.
*/
private final Set<Mechanism> allMechanisms;
@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 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 ) );
}
return new SaslServerPlainImpl(protocol, serverName, props, cbh);
@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;
}
else if (mechanism.equals(myMechs[SCRAM_SHA_1])) {
if (cbh == null) {
throw new SaslException("CallbackHandler with support for AuthorizeCallback required");
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 );
}
/**
* 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;
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 );
}
return result;
if ( session instanceof LocalIncomingServerSession )
{
return new ExternalServerSaslServer( (LocalIncomingServerSession) session );
}
/**
* 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.
*/
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 String[] getMechanismNames(Map<String, ?> props) {
if (checkPolicy(props)) {
return myMechs;
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;
}
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 result.toArray( new String[ result.size() ] );
}
private static class Mechanism
{
final String name;
final boolean allowsAnonymous;
final boolean isPlaintext;
private Mechanism( String name, boolean allowsAnonymous, boolean isPlaintext )
{
this.name = name;
this.allowsAnonymous = allowsAnonymous;
this.isPlaintext = isPlaintext;
}
return new String [] { };
}
}
......@@ -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