blob: c9a5da4be5ce784a924b0788e9acbd709bdc81c6 [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 org.apache.qpid.server.management.plugin;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.zip.GZIPOutputStream;
import javax.security.auth.Subject;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.qpid.server.management.plugin.servlet.ServletConnectionPrincipal;
import org.apache.qpid.server.management.plugin.session.LoginLogoutReporter;
import org.apache.qpid.server.model.Broker;
import org.apache.qpid.server.plugin.QpidServiceLoader;
import org.apache.qpid.server.security.access.Operation;
public class HttpManagementUtil
{
/**
* Servlet context attribute holding a reference to a broker instance
*/
public static final String ATTR_BROKER = "Qpid.broker";
/**
* Servlet context attribute holding a reference to plugin configuration
*/
public static final String ATTR_MANAGEMENT_CONFIGURATION = "Qpid.managementConfiguration";
private static final String ATTR_LOGIN_LOGOUT_REPORTER = "Qpid.loginLogoutReporter";
private static final String ATTR_SUBJECT = "Qpid.subject";
private static final String ATTR_LOG_ACTOR = "Qpid.logActor";
public static final String ACCEPT_ENCODING_HEADER = "Accept-Encoding";
public static final String CONTENT_ENCODING_HEADER = "Content-Encoding";
public static final String GZIP_CONTENT_ENCODING = "gzip";
private static final Collection<HttpRequestPreemptiveAuthenticator> AUTHENTICATORS;
private static final Operation MANAGE_ACTION = Operation.ACTION("manage");
static
{
List<HttpRequestPreemptiveAuthenticator> authenticators = new ArrayList<>();
for(HttpRequestPreemptiveAuthenticator authenticator : (new QpidServiceLoader()).instancesOf(HttpRequestPreemptiveAuthenticator.class))
{
authenticators.add(authenticator);
}
AUTHENTICATORS = Collections.unmodifiableList(authenticators);
}
public static String getRequestSpecificAttributeName(String name, HttpServletRequest request)
{
return name + "." + HttpManagement.getPort(request).getId();
}
public static Broker<?> getBroker(ServletContext servletContext)
{
return (Broker<?>) servletContext.getAttribute(ATTR_BROKER);
}
public static HttpManagementConfiguration getManagementConfiguration(ServletContext servletContext)
{
return (HttpManagementConfiguration) servletContext.getAttribute(ATTR_MANAGEMENT_CONFIGURATION);
}
public static Subject getAuthorisedSubject(HttpServletRequest request)
{
HttpSession session = request.getSession(false);
return (session == null ? null : (Subject) session.getAttribute(getRequestSpecificAttributeName(ATTR_SUBJECT,request)));
}
public static void checkRequestAuthenticatedAndAccessAuthorized(HttpServletRequest request, Broker broker,
HttpManagementConfiguration managementConfig)
{
Subject subject = getAuthorisedSubject(request);
if (subject == null)
{
subject = tryToAuthenticate(request, managementConfig);
if (subject == null)
{
throw new SecurityException("Only authenticated users can access the management interface");
}
subject = createServletConnectionSubject(request, subject);
assertManagementAccess(broker, subject);
saveAuthorisedSubject(request, subject);
}
}
public static Subject createServletConnectionSubject(final HttpServletRequest request, Subject original)
{
Subject subject = new Subject(false,
original.getPrincipals(),
original.getPublicCredentials(),
original.getPrivateCredentials());
subject.getPrincipals().add(new ServletConnectionPrincipal(request));
subject.setReadOnly();
return subject;
}
public static void assertManagementAccess(final Broker<?> broker, Subject subject)
{
Subject.doAs(subject, new PrivilegedAction<Void>()
{
@Override
public Void run()
{
broker.authorise(MANAGE_ACTION);
return null;
}
});
}
public static void saveAuthorisedSubject(HttpServletRequest request, Subject subject)
{
HttpSession session = request.getSession();
session.setAttribute(getRequestSpecificAttributeName(ATTR_SUBJECT, request), subject);
// Cause the user logon to be logged.
session.setAttribute(getRequestSpecificAttributeName(ATTR_LOGIN_LOGOUT_REPORTER, request),
new LoginLogoutReporter(subject, getBroker(session.getServletContext())));
}
public static Subject tryToAuthenticate(HttpServletRequest request, HttpManagementConfiguration managementConfig)
{
Subject subject = null;
for(HttpRequestPreemptiveAuthenticator authenticator : AUTHENTICATORS)
{
subject = authenticator.attemptAuthentication(request, managementConfig);
if(subject != null)
{
break;
}
}
return subject;
}
public static OutputStream getOutputStream(final HttpServletRequest request, final HttpServletResponse response)
throws IOException
{
return getOutputStream(request, response, getManagementConfiguration(request.getServletContext()));
}
public static OutputStream getOutputStream(final HttpServletRequest request, final HttpServletResponse response, HttpManagementConfiguration managementConfiguration)
throws IOException
{
OutputStream outputStream;
if(isCompressingAccepted(request, managementConfiguration))
{
outputStream = new GZIPOutputStream(response.getOutputStream());
response.setHeader(CONTENT_ENCODING_HEADER, GZIP_CONTENT_ENCODING);
}
else
{
outputStream = response.getOutputStream();
}
return outputStream;
}
public static boolean isCompressingAccepted(final HttpServletRequest request,
final HttpManagementConfiguration managementConfiguration)
{
return managementConfiguration.isCompressResponses()
&& Collections.list(request.getHeaderNames()).contains(ACCEPT_ENCODING_HEADER)
&& request.getHeader(ACCEPT_ENCODING_HEADER).contains(GZIP_CONTENT_ENCODING);
}
public static String ensureFilenameIsRfc2183(final String requestedFilename)
{
return requestedFilename.replaceAll("[\\P{InBasic_Latin}\\\\:/\\p{Cntrl}]", "");
}
public static List<String> getPathInfoElements(final String servletPath, final String pathInfo)
{
if (pathInfo == null || pathInfo.length() == 0)
{
return Collections.emptyList();
}
String[] pathInfoElements = pathInfo.substring(1).split("/");
for (int i = 0; i < pathInfoElements.length; i++)
{
try
{
// double decode to allow slashes in object names. first decoding happens in request.getPathInfo().
pathInfoElements[i] = URLDecoder.decode(pathInfoElements[i], StandardCharsets.UTF_8.name());
}
catch (UnsupportedEncodingException e)
{
throw new IllegalArgumentException("Servlet at " + servletPath
+ " could not decode path element: " + pathInfoElements[i], e);
}
}
return Arrays.asList(pathInfoElements);
}
public static String getRequestURL(HttpServletRequest httpRequest)
{
String url;
StringBuilder urlBuilder = new StringBuilder(httpRequest.getRequestURL());
String queryString = httpRequest.getQueryString();
if (queryString != null)
{
urlBuilder.append('?').append(queryString);
}
url = urlBuilder.toString();
return url;
}
public static String getRequestPrincipals(HttpServletRequest httpRequest)
{
HttpSession session = httpRequest.getSession(false);
if (session != null)
{
Subject subject = HttpManagementUtil.getAuthorisedSubject(httpRequest);
if (subject != null)
{
Set<Principal> principalSet = subject.getPrincipals();
if (!principalSet.isEmpty())
{
TreeSet<String> principalNames = new TreeSet();
for (Principal principal : principalSet)
{
principalNames.add(principal.getName());
}
return principalNames.toString();
}
}
}
return null;
}
}