blob: 3859c91c95659c3f8391ad4ac7475f4e87891efa [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.services.http.proxy;
import flex.messaging.FlexContext;
import flex.messaging.services.HTTPProxyService;
import flex.messaging.log.Log;
import flex.messaging.util.Base64;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.StatusLine;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Enumeration;
/**
*
* Handles whitelist/access and authentication/authorization system for requests. Checks URL to make sure we
* accept it. Sets credentials if needed on the request. After the request is made, changes response for
* security info if needed
*/
public class SecurityFilter extends ProxyFilter
{
// NOTE: any changes to this class should also be made to the corresponding version in the .NET.
// The corresponding class is in src/dotNet/libs/FlexASPlib/Aspx/Proxy
private static final int EMPTY_ERROR = 10708;
private static final int ONLY_HTTP_HTTPS = 10712;
private static final int NO_HTTPS_VIA_HTTP = 10713;
private static final int NO_BASIC_NOT_HTTP = 10714;
private static final int NO_BASIC_FOR_SOAP = 10715;
private static final int DOMAIN_ERROR = 10716;
private static final int LOGIN_REQUIRED = 10717;
private static final int UNAUTHORIZED_ERROR = 10718;
public void invoke(ProxyContext context)
{
checkURL(context);
setCredentials(context);
if (next != null)
{
next.invoke(context);
}
sendSecurityInfo(context);
}
private void checkURL(ProxyContext context)
{
Target target = context.getTarget();
// We only allow http type urls
if (!context.getTarget().getUrl().getProtocol().equalsIgnoreCase("http") && !target.isHTTPS())
{
Log.getLogger(HTTPProxyService.LOG_CATEGORY).warn(ProxyConstants.PROXY_SECURITY + ProxyConstants.ONLY_HTTP_HTTPS);
throw new ProxyException(ONLY_HTTP_HTTPS);
}
if (target.isHTTPS() && !context.isClientHttps())
{
// Respond with error
Log.getLogger(HTTPProxyService.LOG_CATEGORY).warn(ProxyConstants.PROXY_SECURITY + ProxyConstants.NO_HTTPS_VIA_HTTP);
throw new ProxyException(NO_HTTPS_VIA_HTTP);
}
}
private void setCredentials(ProxyContext context)
{
String user = null, password = null;
// Check for credentials in runAs
user = context.getTarget().getRemoteUsername();
password = context.getTarget().getRemotePassword();
String fromRequest = null;
HttpServletRequest clientRequest = FlexContext.getHttpRequest();
if (clientRequest != null)
{
// Check for credentials in parameter/header
fromRequest = clientRequest.getHeader(ProxyConstants.HEADER_CREDENTIALS);
}
// We sometimes send the credentials as a URL parameter to work around a player/Opera issue
if (fromRequest == null)
{
fromRequest = context.getCredentialsHeader();
}
// Add authentication header for credentials
if (fromRequest != null)
{
Base64.Decoder decoder = new Base64.Decoder();
decoder.decode(fromRequest);
String decoded = new String(decoder.drain());
int colonIdx = decoded.indexOf(":");
if (colonIdx != -1)
{
user = decoded.substring(0, colonIdx);
}
password = decoded.substring(colonIdx + 1);
}
// Check for existing authentication header
if (clientRequest != null)
{
Enumeration headers = clientRequest.getHeaders("Authorization");
if (headers != null)
{
while (headers.hasMoreElements())
{
String value = (String)headers.nextElement();
if (value.startsWith("Basic"))
{
if (!context.isLocalDomainAndPort())
{
if (Log.isInfo())
{
Log.getLogger(HTTPProxyService.LOG_CATEGORY).debug("Not sending on Authentication header. Proxy domain:port of " +
clientRequest.getServerName() + ":" + clientRequest.getServerPort() + " does not match target domain:port of " +
context.getTarget().getUrl().getHost() + ":" + context.getTarget().getUrl().getPort());
}
}
else
{
// Super gross hack to work around what appears to be an commons-httpclient bug
// where headers are not resent after a 302.
Base64.Decoder decoder = new Base64.Decoder();
String encoded = value.substring(6);
decoder.decode(encoded);
String decoded = new String(decoder.drain());
int colonIdx = decoded.indexOf(":");
user = decoded.substring(0, colonIdx);
password = decoded.substring(colonIdx + 1);
}
}
}
}
}
// Set up request for authentication
if (user != null)
{
UsernamePasswordCredentials cred = new UsernamePasswordCredentials(user, password);
context.getHttpClient().getState().setCredentials(ProxyUtil.getDefaultAuthScope(), cred);
context.setAuthorization(true);
if (Log.isInfo())
{
Log.getLogger(HTTPProxyService.LOG_CATEGORY).info("-- Authentication header being sent for " + user);
}
}
}
private void sendSecurityInfo(ProxyContext context)
{
Target target = context.getTarget();
String targetHost = target.getUrl().getHost();
int statusCode = 200;
boolean customAuth = target.useCustomAuthentication();
StatusLine statusLine = context.getHttpMethod().getStatusLine();
if (statusLine != null)
{
statusCode = statusLine.getStatusCode();
}
context.setStatusCode(statusCode);
if (statusCode == 401 || statusCode == 403)
{
if (!customAuth)
{
if (!context.isHttpRequest())
{
throw new ProxyException(NO_BASIC_NOT_HTTP);
}
else if (context.isSoapRequest())
{
// Note: if we remove this error, must do the proxyDomain/targetHost check as done above
throw new ProxyException(NO_BASIC_FOR_SOAP);
}
else
{
// Don't allow a 401 (and 403, although this should never happen) to be sent to the client
// if the service is not using custom authentication and the domains do not match
if (!context.isLocalDomainAndPort())
{
HttpServletRequest clientRequest = FlexContext.getHttpRequest();
String errorMessage = ProxyConstants.DOMAIN_ERROR + " . The proxy domain:port is " +
clientRequest.getServerName() + ":" + clientRequest.getServerPort() +
" and the target domain:port is " + targetHost + ":" + target.getUrl().getPort();
Log.getLogger(HTTPProxyService.LOG_CATEGORY).error(errorMessage);
throw new ProxyException(DOMAIN_ERROR);
}
else
{
//For BASIC Auth, send back the status code
HttpServletResponse clientResponse = FlexContext.getHttpResponse();
clientResponse.setStatus(statusCode);
}
}
}
else
{
String message = null;
if (statusLine != null)
message = statusLine.toString();
if (statusCode == 401)
{
ProxyException se = new ProxyException();
se.setCode("Client.Authentication");
if (message == null)
{
se.setMessage(LOGIN_REQUIRED);
}
else
{
se.setMessage(EMPTY_ERROR, new Object[] { message });
}
Header header = context.getHttpMethod().getResponseHeader(ProxyConstants.HEADER_AUTHENTICATE);
if (header != null)
se.setDetails(header.getValue());
throw se;
}
else
{
ProxyException se = new ProxyException();
se.setCode("Client.Authentication");
if (message == null)
{
se.setMessage(UNAUTHORIZED_ERROR);
}
else
{
se.setMessage(EMPTY_ERROR, new Object[] { message });
}
throw se;
}
}
}
}
}