blob: 1f42ab62452c482df72fdd681ad93cd6645190f8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 flex.messaging.security;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Principal;
import java.util.List;
import flex.messaging.FlexComponent;
import flex.messaging.FlexContext;
import flex.messaging.FlexSession;
import flex.messaging.HttpFlexSession;
import flex.messaging.config.ConfigMap;
import flex.messaging.config.ConfigurationException;
import flex.messaging.config.SecurityConstraint;
import flex.messaging.endpoints.Endpoint;
import flex.messaging.log.Log;
import flex.messaging.log.LogCategories;
/**
* Much of this logic has been taken from the Flash Remoting Gateway.
* <p>
* Since each application server manages sessions, users and security
* differently, a separate LoginCommand needs to be written for
* each server.
* </p>
*
*
*/
public class LoginManager implements FlexComponent
{
/** Log category for LoginManager. */
public static final String LOG_CATEGORY = LogCategories.SECURITY;
private static final String NIOHTTP_FLEX_SESSION_TYPE = "flex.messaging.endpoints.NIOHTTPFlexSession";
private static final String INVALIDATE_METHOD = "invalidate";
// Exception/error message numbers.
private static final int INVALID_LOGIN = 10050;
private static final int LOGIN_REQ = 10051;
private static final int NO_LOGIN_COMMAND = 10053;
private static final int CANNOT_REAUTH = 10054;
private static final int ACCESS_DENIED = 10055;
private static final int LOGIN_REQ_FOR_AUTH = 10056;
private static final int NO_BASIC_SECURITY = 10057;
private static final int PER_CLIENT_ANT_APPSERVER = 10065;
private LoginCommand loginCommand;
private boolean perClientAuthentication;
private boolean started;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Creates a new <code>LoginManager</code> instance.
*/
public LoginManager()
{
perClientAuthentication = false;
}
//--------------------------------------------------------------------------
//
// Initialize, validate, start, and stop methods.
//
//--------------------------------------------------------------------------
/**
* Implements FlexComponents.initialize.
* This is no-op for LoginManager as it does not have an id and all
* its properties are directly settable.
*
* @param id The id of the component.
* @param configMap The properties for configuring component.
*/
public void initialize(String id, ConfigMap configMap)
{
// No-op
}
/**
* Validates the LoginManager before it is started.
*/
protected void validate()
{
if (perClientAuthentication && loginCommand instanceof AppServerLoginCommand)
{
// Cannot use application server authentication together with per client authentication.
ConfigurationException configException = new ConfigurationException();
configException.setMessage(PER_CLIENT_ANT_APPSERVER);
throw configException;
}
}
/**
* Implements FlexComponent.start.
* Starts the <code>LoginManager</code>.
*/
public void start()
{
if (started)
return;
validate();
if (loginCommand != null)
loginCommand.start(FlexContext.getServletConfig());
started = true;
}
/**
* Implements FlexComponents.stop.
* Stops the <code>LoginManager</code>.
*/
public void stop()
{
if (!started)
return;
if (loginCommand != null)
loginCommand.stop();
started = false;
}
//--------------------------------------------------------------------------
//
// Public Methods
//
//--------------------------------------------------------------------------
/**
* Returns whether per client authentication is enabled or not.
*
* @return <code>true</code> if per client authentication is enabled;
* otherwise <code>false</code>.
*/
public boolean isPerClientAuthentication()
{
return perClientAuthentication;
}
/**
* Sets whether per client authentication is enabled or not.
*
* @param perClientAuthentication <code>true</code> if per client authentication
* is enabled; otherwise <code>false</code>.
*/
public void setPerClientAuthentication(boolean perClientAuthentication)
{
this.perClientAuthentication = perClientAuthentication;
}
/**
* Implements FlexComponent.isStarted.
* Returns whether the LoginManager is started or not.
*
* @return <code>true</code> if the LoginManager is started; otherwise <code>false</code>.
*/
public boolean isStarted()
{
return started;
}
/**
* Returns the login command used.
*
* @return loginCommand The login command used.
*/
public LoginCommand getLoginCommand()
{
return loginCommand;
}
/**
* Sets the login command used.
*
* @param value The login command to set.
*/
public void setLoginCommand(LoginCommand value)
{
if (loginCommand == value)
return;
loginCommand = value;
if (started)
loginCommand.start(FlexContext.getServletConfig());
}
/**
* Perform login with username and credentials.
*
* @param username Username to use to login.
* @param credentials Credentials to use to login.
*/
public void login(String username, Object credentials)
{
if (getCurrentPrincipal() == null)
{
if (loginCommand == null)
{
// Client needs to be externally authenticated via Basic Authentication or some other method.
SecurityException se = new SecurityException();
se.setMessage(NO_LOGIN_COMMAND);
se.setCode(SecurityException.SERVER_AUTHENTICATION_CODE);
throw se;
}
if (username != null && credentials != null)
{
Principal authenticated = loginCommand.doAuthentication(username, credentials);
if (authenticated == null) // Invalid login.
{
SecurityException se = new SecurityException();
se.setMessage(INVALID_LOGIN);
se.setCode(SecurityException.CLIENT_AUTHENTICATION_CODE);
throw se;
}
setCurrentPrincipal(authenticated);
}
else
{
// Login is required but the client passed null principal and credentials.
SecurityException se = new SecurityException();
se.setMessage(LOGIN_REQ);
se.setCode(SecurityException.CLIENT_AUTHENTICATION_CODE);
throw se;
}
}
else
{
// It is possible that the username passed in from the client and that stored in the
// Principal on the session may be different. To facilitate this case a LoginCommand
// must implement LoginCommandExt and the user stored in the Principal is retrieved
// here for comparison.
String comparisonUsername = loginCommand instanceof LoginCommandExt?
((LoginCommandExt)loginCommand).getPrincipalNameFromCredentials(username, credentials) : username;
// If we have a username and a different existing principal then we must raise an exception
// as we don't allow re-authentication for a given session.
if (comparisonUsername != null && !comparisonUsername.equals(getCurrentPrincipal().getName()))
{
// Cannot re-authenticate in the same session.
SecurityException se = new SecurityException();
se.setMessage(CANNOT_REAUTH);
se.setCode(SecurityException.CLIENT_AUTHENTICATION_CODE);
throw se;
}
}
}
/**
* Perform logout.
*/
public void logout()
{
if (loginCommand == null)
{
FlexContext.getFlexSession().invalidate();
// External login command required. Please check your security configuration.
SecurityException se = new SecurityException();
se.setMessage(NO_LOGIN_COMMAND);
se.setCode(SecurityException.SERVER_AUTHORIZATION_CODE);
throw se;
}
// Always invoke the command's logout hook.
loginCommand.logout(getCurrentPrincipal());
if (FlexContext.isPerClientAuthentication())
{
FlexContext.setUserPrincipal(null);
}
else
{
FlexSession session = FlexContext.getFlexSession();
session.invalidate();
}
}
/**
* Throws various <code>SecurityException</code>s depending on whether an authenticated user
* is associated with the current session and if one is whether the passed constraint prohibits
* this user from performing an operation. If a valid user is found and passed the constraint,
* no exceptions are thrown
*
* @param constraint Constraint against which the current user is authorized.
*/
public void checkConstraint(SecurityConstraint constraint)
{
if (constraint == null)
return;
Principal currentPrincipal = getCurrentPrincipal();
if (currentPrincipal != null)
{
List roles = constraint.getRoles();
boolean authorized = roles == null || checkRoles(currentPrincipal, roles);
if (!authorized)
{
// Access denied. User not authorized.
SecurityException se = new SecurityException();
se.setMessage(ACCESS_DENIED);
se.setCode(SecurityException.CLIENT_AUTHORIZATION_CODE);
throw se;
}
}
else
{
if (!isCustomAuth(constraint))
{
// Some endpoints (NIO) do not support HTTP Basic authentication.
if (FlexContext.getHttpResponse() == null)
{
Endpoint endpoint = FlexContext.getEndpoint();
String endpointId = (endpoint != null) ? endpoint.getId() : "unknown";
// A resource protected by a security constraint that specifies Basic security was accessed via the ''{0}''
// endpoint which does not support HTTP Basic security. Please use custom security or an alternate endpoint.
SecurityException se =new SecurityException();
se.setMessage(NO_BASIC_SECURITY, new Object[] {constraint.getId(), endpointId});
se.setCode(SecurityException.CLIENT_AUTHORIZATION_CODE);
throw se;
}
// What goes back will cause basic user dialog
FlexContext.getHttpResponse().setStatus(401);
FlexContext.getHttpResponse().addHeader("WWW-Authenticate", "Basic realm=\"default\"");
}
// Login required before authorization can proceed.
SecurityException se = new SecurityException();
se.setMessage(LOGIN_REQ_FOR_AUTH);
se.setCode(SecurityException.CLIENT_AUTHENTICATION_CODE);
throw se;
}
}
/**
* Returns true if the passed in principal belongs to at least one of the
* roles in the passed in list of roles.
*
* @param principal Principal to check against roles
* @param roles list of roles
* @return true if principal belongs to at least one of the roles in the list
*/
public boolean checkRoles(Principal principal, List roles)
{
if (loginCommand == null) // This should not happen but just in case.
{
if (Log.isWarn())
Log.getLogger(LOG_CATEGORY).warn
("Login command is null. Please ensure that the login-command"
+ " tag has the correct server attribute value"
+ ", or use 'all' to use the login command regardless of the server.");
return false;
}
return loginCommand.doAuthorization(principal, roles);
}
//--------------------------------------------------------------------------
//
// Protected and Private methods
//
//--------------------------------------------------------------------------
private Principal getCurrentPrincipal()
{
return FlexContext.getUserPrincipal();
}
private void setCurrentPrincipal(Principal p)
{
FlexContext.setUserPrincipal(p);
}
private boolean isCustomAuth(SecurityConstraint constraint)
{
return SecurityConstraint.CUSTOM_AUTH_METHOD.equals(constraint.getMethod());
}
}