/* | |
* 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 flex.messaging.log.Log; | |
import flex.messaging.log.LogCategories; | |
import flex.messaging.util.ExceptionUtil; | |
import java.io.IOException; | |
import java.security.Principal; | |
import java.util.Iterator; | |
import java.util.List; | |
import javax.servlet.ServletException; | |
import javax.servlet.ServletRequest; | |
import javax.servlet.http.HttpServletRequest; | |
import javax.servlet.http.HttpSession; | |
import org.apache.catalina.Container; | |
import org.apache.catalina.Lifecycle; | |
import org.apache.catalina.LifecycleException; | |
import org.apache.catalina.LifecycleListener; | |
import org.apache.catalina.Manager; | |
import org.apache.catalina.Realm; | |
import org.apache.catalina.Session; | |
import org.apache.catalina.Valve; | |
import org.apache.catalina.authenticator.Constants; | |
import org.apache.catalina.connector.Request; | |
import org.apache.catalina.connector.Response; | |
import org.apache.catalina.valves.ValveBase; | |
/** | |
* | |
* A Tomcat valve for allowing programmatic login. This valve saves the container, | |
* something not available normally to a servlet, and allows login to the current realm. | |
* The pieces interacting with Tomcat are taken from org.apache.catalina.authenticator.AuthenticatorBase. | |
* It would be nice if we could just extend that class or call some of its methods, | |
* but things aren't set up in that class in such a way that this is possible | |
* | |
* FIXME: Doesn't support Tomcat's SingleSignOn which is a way to write custom valves that associate | |
* the principal to different web apps or locations. See AuthenticatorBase for details | |
*/ | |
public class TomcatValve extends ValveBase implements Lifecycle | |
{ | |
private static final String AUTH_TYPE = "flexmessaging"; // was "flashgateway" | |
private static final String AMF_MATCH = "/amfgateway"; | |
private static final String GATEWAY_MATCH = "/flashgateway"; | |
private static final String MESSAGEBROKER_MATCH = "/messagebroker"; | |
private static String CUSTOM_MATCH = System.getProperty("flex.tomcatValveMatch"); | |
public void invoke(Request request, Response response) throws IOException, ServletException | |
{ | |
invokeServletRequest(request); | |
Valve next = getNext(); | |
if (next != null) | |
next.invoke(request, response); | |
} | |
private void invokeServletRequest(Request request) | |
{ | |
ServletRequest servRequest = request.getRequest(); | |
if (!(servRequest instanceof HttpServletRequest)) | |
return; | |
// We only set the TomcatLoginImpl for gateway paths | |
HttpServletRequest hrequest = (HttpServletRequest)servRequest; | |
boolean match = checkIfPathMatches(hrequest.getServletPath(), hrequest.getRequestURI()); | |
if (match) | |
handleMatch(request, hrequest.getUserPrincipal()); | |
} | |
private void handleMatch(Request request, Principal principal) | |
{ | |
TomcatLoginHolder.setLogin(new TomcatLoginImpl(getContainer(), request)); | |
// Copy over user principal and auth type values, just like in AuthenticatorBase.invoke() | |
if (principal != null) | |
return; | |
Session session = getSession(request, false); | |
if (session == null) | |
return; | |
principal = session.getPrincipal(); | |
if (principal != null) | |
{ | |
request.setAuthType(session.getAuthType()); | |
request.setUserPrincipal(principal); | |
} | |
} | |
private boolean checkIfPathMatches(String path, String uri) | |
{ | |
if (path == null) | |
{ | |
// We need to use a slighly-weaker uri match for 4.1 | |
return (uri != null && | |
(uri.indexOf(MESSAGEBROKER_MATCH) != -1 || | |
uri.indexOf(AMF_MATCH) != -1 || | |
uri.indexOf(GATEWAY_MATCH) != -1 || | |
(CUSTOM_MATCH != null && uri.indexOf(CUSTOM_MATCH) != -1))); | |
} | |
else | |
{ | |
return (path.startsWith(MESSAGEBROKER_MATCH) || | |
path.startsWith(AMF_MATCH) || | |
path.startsWith(GATEWAY_MATCH) || | |
(CUSTOM_MATCH != null && path.startsWith(CUSTOM_MATCH))); | |
} | |
} | |
public void addLifecycleListener(LifecycleListener listener) | |
{ | |
// No-op. | |
} | |
public LifecycleListener[] findLifecycleListeners() | |
{ | |
return null; | |
} | |
public void removeLifecycleListener(LifecycleListener listener) | |
{ | |
// No-op. | |
} | |
public void start() throws LifecycleException | |
{ | |
// RTMP may not go through invoke so we need to put at least one TomcatLoginImpl in the holder. | |
TomcatLogin login = new TomcatLoginImpl(getContainer(), null); | |
TomcatLoginHolder.setLogin(login); | |
// To avoid the thread processes the nio based endpoints does not match the thread start the valve (which is quite possible in Tomcat) | |
// We set the singleton | |
TomcatLoginHolder.setNioBasedLogin(login); | |
} | |
public void stop() throws LifecycleException | |
{ | |
// No-op. | |
} | |
// from AuthenticatorBase.getSession() | |
static Session getSession(Request request, boolean create) | |
{ | |
HttpServletRequest hreq = (HttpServletRequest)request.getRequest(); | |
HttpSession hses = hreq.getSession(create); | |
if (hses == null) | |
return null; | |
Manager manager = request.getContext().getManager(); | |
if (manager == null) | |
return null; | |
try | |
{ | |
return manager.findSession(hses.getId()); | |
} | |
catch (IOException e) | |
{ | |
Log.getLogger(LogCategories.SECURITY).error("Error in TomcatValve getting session id " + hses.getId() + " : " + ExceptionUtil.toString(e)); | |
return null; | |
} | |
} | |
class TomcatLoginImpl implements TomcatLogin | |
{ | |
private Container container; | |
private Request request; | |
TomcatLoginImpl(Container container, Request request) | |
{ | |
this.container = container; | |
this.request = request; | |
} | |
// Authenticate the user and associate with the current session. | |
// This is taken from AuthenticatorBase.register() | |
public Principal login(String username, String password, HttpServletRequest servletRequest) | |
{ | |
Realm realm = container.getRealm(); | |
if (realm == null) | |
return null; | |
Principal principal = realm.authenticate(username, password); | |
if (principal == null) | |
return null; | |
if (servletRequestMatches(servletRequest)) | |
{ | |
request.setAuthType(AUTH_TYPE); | |
request.setUserPrincipal(principal); | |
Session session = getSession(request, true); | |
// Cache the authentication information in our session. | |
if (session != null) | |
{ | |
session.setAuthType(AUTH_TYPE); | |
session.setPrincipal(principal); | |
if (username != null) | |
session.setNote(Constants.SESS_USERNAME_NOTE, username); | |
else | |
session.removeNote(Constants.SESS_USERNAME_NOTE); | |
if (password != null) | |
session.setNote(Constants.SESS_PASSWORD_NOTE, password); | |
else | |
session.removeNote(Constants.SESS_PASSWORD_NOTE); | |
} | |
} | |
return principal; | |
} | |
public boolean authorize(Principal principal, List roles) | |
{ | |
Realm realm = container.getRealm(); | |
Iterator iter = roles.iterator(); | |
while (iter.hasNext()) | |
{ | |
String role = (String)iter.next(); | |
if (realm.hasRole(principal, role)) | |
return true; | |
} | |
return false; | |
} | |
public boolean logout(HttpServletRequest servletRequest) | |
{ | |
if (servletRequestMatches(servletRequest)) | |
{ | |
Session session = getSession(request, false); | |
if (session != null) | |
{ | |
session.setPrincipal(null); | |
session.setAuthType(null); | |
session.removeNote(Constants.SESS_USERNAME_NOTE); | |
session.removeNote(Constants.SESS_PASSWORD_NOTE); | |
} | |
return true; | |
} | |
return false; | |
} | |
private boolean servletRequestMatches(HttpServletRequest servletRequest) | |
{ | |
return request != null && request.getRequest() == servletRequest; | |
} | |
/** {@inheritDoc} */ | |
public Principal convertPrincipal(Principal principal) | |
{ | |
return principal; | |
} | |
} | |
} |