package com.limegroup.gnutella.handshaking;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;
import com.limegroup.gnutella.ConnectionManager;
import com.limegroup.gnutella.security.Cookies;
import com.limegroup.gnutella.security.User;
import com.limegroup.gnutella.settings.SecuritySettings;
import com.limegroup.gnutella.util.StringUtils;
/**
* An authentication-capable responder to be used during handshake.
* This is an abstract class, and provides only authentication
* capablities.
* <p> Concrete subclasses should implement the respondUnAuthenticated()
* method for the actual handshake (apart from authentication).
* <p> The public respond(response, outgoing) method should not be
* overwritten before taking this statement out.
*/
public abstract class AuthenticationHandshakeResponder
implements HandshakeResponder
{
/**
* Constant handle to the <tt>Cookies</tt> for authentication
* purposes
*/
private final Cookies COOKIES = Cookies.instance();
/**
* Constant handle to the <tt>SettingsManager</tt> for accessing
* various properties.
*/
//private final SettingsManager SETTINGS = SettingsManager.instance();
/**
* An instance of connection manager (to reference other stuff
* held by connection manager)
*/
protected final ConnectionManager _manager;
/**
* The host to which are opening connection
*/
private final String _host;
/**
* Flag indicating whether we have used the cookie for authentication
*/
private boolean _cookieUsed = false;
/**
* Flag indicating whether he user connecting to us, has authenticated
*/
private boolean _authenticated = false;
/**
* The request received before we asked other node to authenticate
*/
private HandshakeResponse _beforeAuthenticationRequest = null;
/**
* Creates a new instance
* @param manager Instance of connection manager, managing this
* connection
* @param host The host with whom we are handshaking
*/
public AuthenticationHandshakeResponder(ConnectionManager manager, String host)
{
this._manager = manager;
this._host = host;
}
public HandshakeResponse respond(HandshakeResponse response, boolean outgoing)
throws IOException
{
//save the first request
if(_beforeAuthenticationRequest == null)
_beforeAuthenticationRequest = response;
//do stuff specific to connection direction
if(outgoing)
{
return respondOutgoing(response);
}else
{
return respondIncoming(response);
}
}
/**
* Returns the Remote IP
*/
protected String getRemoteIP()
{
return _host;
}
/**
* Returns the corresponding handshake to be written to the remote host
* when responding to the connection handshake response just received,
* for outgoing connection.
* @param response The handshake response received from the remote end
*/
private HandshakeResponse respondIncoming(HandshakeResponse response)
throws IOException
{
return respondUnauthenticated(response, false);
}
/**
* Asks the connecting host for authentication, if authentication
* headers not already received in the response from the connecting
* host. If authenticated, lets the concrete class handle the
* original request, and returns the response returned by
* the concrete class, augmented with authentication information.
* @param response The handshake response received from the remote end
* @return response Our response
*/
private HandshakeResponse respondIncomingAuthenticate(
HandshakeResponse response) throws IOException
{
//get the domains, user has successfully authenticated
Set domains = getDomainsAuthenticated(response.props());
//if couldnt authenticate
if(domains == null) {
return new HandshakeResponse(HandshakeResponse.UNAUTHORIZED_CODE,
HandshakeResponse.UNAUTHORIZED_MESSAGE,
null);
} else {
_authenticated = true;
//handle the original request
HandshakeResponse ourResponse =
respondUnauthenticated(_beforeAuthenticationRequest, false);
//add the property in the response letting the
//remote host know of the domains successfully authenticated
ourResponse.props().put(HeaderNames.X_DOMAINS_AUTHENTICATED,
StringUtils.getEntriesAsString(domains));
//return our response
return ourResponse;
}
}
/**
* Returns the domains to which the user has successfully
* authenticated to
* @param headersReceived The headers we received. These will be
* used for authentication
* @return the domains to which the user has successfully
* authenticated to
*/
private Set getDomainsAuthenticated(Properties headersReceived)
{
//pass the username and password to authenticator
return
_manager.getAuthenticator().authenticate(
headersReceived.getProperty(HeaderNames.X_USERNAME),
headersReceived.getProperty(HeaderNames.X_PASSWORD), null);
}
/**
* Returns the corresponding handshake to be written to the remote host
* when responding to the connection handshake response just received,
* for outgoing connection.
* @param response The handshake response received from the remote end
*/
private HandshakeResponse respondOutgoing(HandshakeResponse response)
throws IOException
{
//check the code we received from the other side
//if authentication is not needed, respond normally, else
//authenticate
if(response.getStatusCode() != HandshakeResponse.UNAUTHORIZED_CODE)
return respondUnauthenticated(response, true);
else
return respondOutgoingWithAuthentication();
}
/**
* Returns the corresponding handshake to be written to the remote host
* on an outgoing connection, when authentication challenge received
* @param response The handshake response received from the remote end
* @return response Our response
*/
private HandshakeResponse respondOutgoingWithAuthentication()
throws IOException
{
int code = HandshakeResponse.OK;
String message = HandshakeResponse.OK_MESSAGE;
Properties ret = new Properties();
//Authenticate
//get user information
User user = getUserInfo();
//if user is unable to authenticate
if((user == null) || user.getUsername().trim().equals(""))
{
//set the error codes in the response
code = HandshakeResponse.DEFAULT_BAD_STATUS_CODE;
message = HandshakeResponse.UNABLE_TO_AUTHENTICATE;
}
else
{
//set the user properties as well as response headers
code = HandshakeResponse.OK;
message = HandshakeResponse.AUTHENTICATING;
//add user authentication headers
ret.put(HeaderNames.X_USERNAME,
user.getUsername());
ret.put(HeaderNames.X_PASSWORD,
user.getPassword());
//also store the authentication information in a
//cookie, for next-time use
COOKIES.putCookie(_host, user);
}
return new HandshakeResponse(code, message, ret);
}
/**
* Gets the user's authentication information for the host we are
* handshaking with
* @return User's authentication information for the host we are
* handshaking with
*/
private User getUserInfo()
{
User user = null;
//first try the information stored in cookie
if(!_cookieUsed)
{
_cookieUsed = true;
if(_host != null)
user = COOKIES.getUserInfo(_host);
}
//if we dont have cookie, or we have already used the
//cookie, then get the information interactively from user
if(user == null)
{
//user = RouterService.getCallback().getUserAuthenticationInfo(_host);
}
//return the user information
return user;
}
/**
* optional method empty impl.
* Should override if preferencing is wanted.
*/
public void setLocalePreferencing(boolean b) {
//should this throw UnsupportedOperationException?
}
/**
* Returns the corresponding handshake to be sent
* to the remote host when
* responding to the connection handshake response received.
* @param response The response received from the host on the
* other side of the connection.
* @param outgoing whether the connection to the remote host
* is an outgoing connection.
* @param includeProperties The properties that should be included
* in the returned response
* @return the response to be sent to the remote host
*/
protected abstract HandshakeResponse respondUnauthenticated(
HandshakeResponse response, boolean outgoing) throws IOException;
}