diff options
10 files changed, 305 insertions, 26 deletions
diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/Management.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/Management.java index 0dc1800577..dfacf24928 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/Management.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/Management.java @@ -33,6 +33,7 @@ import org.apache.qpid.server.logging.messages.ManagementConsoleMessages; import org.apache.qpid.server.management.plugin.servlet.DefinedFileServlet; import org.apache.qpid.server.management.plugin.servlet.FileServlet; import org.apache.qpid.server.management.plugin.servlet.rest.LogRecordsServlet; +import org.apache.qpid.server.management.plugin.servlet.rest.LogoutServlet; import org.apache.qpid.server.management.plugin.servlet.rest.MessageContentServlet; import org.apache.qpid.server.management.plugin.servlet.rest.MessageServlet; import org.apache.qpid.server.management.plugin.servlet.rest.RestServlet; @@ -67,10 +68,12 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; public class Management { - private static final String OPERATIONAL_LOGGING_NAME = "Web"; - private final Logger _logger = Logger.getLogger(Management.class); + public static final String ENTRY_POINT_PATH = "/management"; + + private static final String OPERATIONAL_LOGGING_NAME = "Web"; + private final Broker _broker; private final Collection<Server> _servers = new ArrayList<Server>(); @@ -125,6 +128,7 @@ public class Management + (sslPort == -1 ? "" : " HTTPS port " + sslPort)); } + IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); Server server = new Server(); if (port != -1) @@ -140,7 +144,6 @@ public class Management if (sslPort != -1) { - IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); String keyStorePath = getKeyStorePath(appRegistry); SslContextFactory factory = new SslContextFactory(); @@ -178,7 +181,8 @@ public class Management root.addServlet(new ServletHolder(new SaslServlet(_broker)), "/rest/sasl"); - root.addServlet(new ServletHolder(new DefinedFileServlet("index.html")), "/management"); + root.addServlet(new ServletHolder(new DefinedFileServlet("index.html")), ENTRY_POINT_PATH); + root.addServlet(new ServletHolder(new LogoutServlet()), "/logout"); root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.js"); root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.css"); @@ -193,7 +197,7 @@ public class Management final SessionManager sessionManager = root.getSessionHandler().getSessionManager(); - sessionManager.setMaxInactiveInterval(60 * 15); + sessionManager.setMaxInactiveInterval(appRegistry.getConfiguration().getHTTPManagementSessionTimeout()); return server; } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java index 77725f6e0c..2dc819cb90 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java @@ -18,7 +18,6 @@ * under the License. * */ - package org.apache.qpid.server.management.plugin.servlet.rest; import java.io.IOException; @@ -41,6 +40,7 @@ import org.apache.qpid.server.logging.LogActor; import org.apache.qpid.server.logging.RootMessageLogger; import org.apache.qpid.server.logging.actors.CurrentActor; import org.apache.qpid.server.logging.actors.HttpManagementActor; +import org.apache.qpid.server.management.plugin.session.LoginLogoutReporter; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.registry.ApplicationRegistry; import org.apache.qpid.server.security.SubjectCreator; @@ -50,6 +50,8 @@ import org.apache.qpid.server.security.auth.manager.AnonymousAuthenticationManag public abstract class AbstractServlet extends HttpServlet { + private static final String ATTR_LOGIN_LOGOUT_REPORTER = "attrLoginLogoutReporter"; + private static final Logger LOGGER = Logger.getLogger(AbstractServlet.class); protected static final String ATTR_SUBJECT = "subject"; @@ -292,7 +294,7 @@ public abstract class AbstractServlet extends HttpServlet if (subject != null) { - setSubjectInSession(subject, session); + setSubjectInSession(subject, request, session); } else { @@ -327,7 +329,7 @@ public abstract class AbstractServlet extends HttpServlet HttpManagementActor actor = (HttpManagementActor) session.getAttribute(ATTR_LOG_ACTOR); if(actor == null) { - actor = new HttpManagementActor(_rootLogger, req.getRemoteAddr(), req.getRemotePort()); + actor = createHttpManagementActor(req); session.setAttribute(ATTR_LOG_ACTOR, actor); } @@ -339,9 +341,13 @@ public abstract class AbstractServlet extends HttpServlet return (Subject)session.getAttribute(ATTR_SUBJECT); } - protected void setSubjectInSession(Subject subject, final HttpSession session) + protected void setSubjectInSession(Subject subject, HttpServletRequest request, final HttpSession session) { session.setAttribute(ATTR_SUBJECT, subject); + + LogActor logActor = createHttpManagementActor(request); + // Cause the user logon to be logged. + session.setAttribute(ATTR_LOGIN_LOGOUT_REPORTER, new LoginLogoutReporter(logActor, subject)); } protected Broker getBroker() @@ -353,4 +359,10 @@ public abstract class AbstractServlet extends HttpServlet { return InetSocketAddress.createUnresolved(request.getServerName(), request.getServerPort()); } + + private HttpManagementActor createHttpManagementActor(HttpServletRequest request) + { + return new HttpManagementActor(_rootLogger, request.getRemoteAddr(), request.getRemotePort()); + } + } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogoutServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogoutServlet.java new file mode 100644 index 0000000000..a5c858e5fe --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogoutServlet.java @@ -0,0 +1,65 @@ +/* + * + * 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.servlet.rest; + +import java.io.IOException; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.qpid.server.management.plugin.Management; + +@SuppressWarnings("serial") +public class LogoutServlet extends HttpServlet +{ + public static final String RETURN_URL_INIT_PARAM = "qpid.webui_logout_redirect"; + private String _returnUrl = Management.ENTRY_POINT_PATH; + + @Override + public void init(ServletConfig config) throws ServletException + { + super.init(config); + + String initValue = config.getServletContext().getInitParameter(RETURN_URL_INIT_PARAM); + if(initValue != null) + { + _returnUrl = initValue; + } + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException + { + HttpSession session = request.getSession(false); + if(session != null) + { + // Invalidating the session will cause LoginLogoutReporter to log the user logoff. + session.invalidate(); + } + + resp.sendRedirect(_returnUrl); + } + +} diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java index e6cfade772..feedc283a9 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java @@ -146,7 +146,7 @@ public class SaslServlet extends AbstractServlet if(id == null) { SaslServer saslServer = subjectCreator.createSaslServer(mechanism, request.getServerName(), null/*TODO*/); - evaluateSaslResponse(response, session, saslResponse, saslServer, subjectCreator); + evaluateSaslResponse(request, response, session, saslResponse, saslServer, subjectCreator); } else { @@ -163,7 +163,7 @@ public class SaslServlet extends AbstractServlet if(id.equals(session.getAttribute(ATTR_ID)) && System.currentTimeMillis() < (Long) session.getAttribute(ATTR_EXPIRY)) { SaslServer saslServer = (SaslServer) session.getAttribute(ATTR_SASL_SERVER); - evaluateSaslResponse(response, session, saslResponse, saslServer, subjectCreator); + evaluateSaslResponse(request, response, session, saslResponse, saslServer, subjectCreator); } else { @@ -212,9 +212,9 @@ public class SaslServlet extends AbstractServlet } } - private void evaluateSaslResponse(final HttpServletResponse response, - final HttpSession session, - final String saslResponse, final SaslServer saslServer, SubjectCreator subjectCreator) throws IOException + private void evaluateSaslResponse(final HttpServletRequest request, + final HttpServletResponse response, + final HttpSession session, final String saslResponse, final SaslServer saslServer, SubjectCreator subjectCreator) throws IOException { final String id; byte[] challenge; @@ -236,7 +236,8 @@ public class SaslServlet extends AbstractServlet { Subject subject = subjectCreator.createSubjectWithGroups(saslServer.getAuthorizationID()); - setSubjectInSession(subject, session); + setSubjectInSession(subject, request, session); + session.removeAttribute(ATTR_ID); session.removeAttribute(ATTR_SASL_SERVER); session.removeAttribute(ATTR_EXPIRY); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/session/LoginLogoutReporter.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/session/LoginLogoutReporter.java new file mode 100644 index 0000000000..238f1b4719 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/session/LoginLogoutReporter.java @@ -0,0 +1,103 @@ +/* + * 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.session; + +import java.security.Principal; +import java.security.PrivilegedAction; + +import javax.security.auth.Subject; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.messages.ManagementConsoleMessages; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; + +/** + * Logs {@link ManagementConsoleMessages#OPEN(String)} and {@link ManagementConsoleMessages#CLOSE(String)} + * messages. A single instance of this class must be placed in the {@link HttpSession} immediately after + * the user has successfully logged-in, and removed (or the whole session invalidated) as the user logs out. + */ +public class LoginLogoutReporter implements HttpSessionBindingListener +{ + private static final Logger LOGGER = Logger.getLogger(LoginLogoutReporter.class); + private final LogActor _logActor; + private final Subject _subject; + private final Principal _principal; + + public LoginLogoutReporter(LogActor logActor, Subject subject) + { + super(); + _logActor = logActor; + _subject = subject; + _principal = AuthenticatedPrincipal.getAuthenticatedPrincipalFromSubject(_subject); + } + + @Override + public void valueBound(HttpSessionBindingEvent arg0) + { + reportLogin(); + } + + @Override + public void valueUnbound(HttpSessionBindingEvent arg0) + { + reportLogout(); + } + + private void reportLogin() + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("User logging in : " + _principal); + } + + Subject.doAs(_subject, new PrivilegedAction<Void>() + { + @Override + public Void run() + { + _logActor.message(ManagementConsoleMessages.OPEN(_principal.getName())); + return null; + } + }); + } + + private void reportLogout() + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("User logging out : " + _principal); + } + + Subject.doAs(_subject, new PrivilegedAction<Void>() + { + @Override + public Void run() + { + _logActor.message(ManagementConsoleMessages.CLOSE(_principal.getName())); + return null; + } + }); + } + +} diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html index a8345a8503..2fb9137ff8 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html @@ -73,10 +73,8 @@ <div id="pageLayout" data-dojo-type="dijit.layout.BorderContainer" data-dojo-props="design: 'headline', gutters: false"> <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'top'"> - <div id="header" class="header"></div> - </div> - <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'top'"> - <div id="login"></div> + <div id="header" class="header" style="float: left; width: 300px"></div> + <div id="login" style="float: right"></div> </div> <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'leading', splitter: true"> <div qpid-type="treeView" qpid-props="query: 'rest/structure'" ></div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/sasl.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/sasl.js index 152504da86..f003b896eb 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/sasl.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/sasl.js @@ -170,13 +170,13 @@ var updateAuthentication = function updateAuthentication() if(data.user) { dojo.byId("authenticatedUser").innerHTML = data.user; - dojo.style(button.domNode, {visibility: 'hidden'}); - dojo.style(usernameSpan, {visibility: 'visible'}); + dojo.style(button.domNode, {display: 'none'}); + dojo.style(usernameSpan, {display: 'block'}); } else { - dojo.style(button.domNode, {visibility: 'visible'}); - dojo.style(usernameSpan, {visibility: 'hidden'}); + dojo.style(button.domNode, {display: 'block'}); + dojo.style(usernameSpan, {display: 'none'}); } } ); @@ -198,13 +198,13 @@ require(["dijit/form/DropDownButton", "dijit/TooltipDialog", "dijit/form/TextBox dropDown: dialog }); - usernameSpan = domConstruct.create("span", { innerHTML: '<strong>User: </strong><span id="authenticatedUser"></span>', - style: { visibility: "hidden" }}); + usernameSpan = domConstruct.create("span", { innerHTML: '<strong>User: </strong> <span id="authenticatedUser"></span><a href="logout">[logout]</a>', + style: { display: "none" }}); var loginDiv = dom.byId("login"); - loginDiv.appendChild(button.domNode); loginDiv.appendChild(usernameSpan); + loginDiv.appendChild(button.domNode); diff --git a/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/session/LoginLogoutReporterTest.java b/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/session/LoginLogoutReporterTest.java new file mode 100644 index 0000000000..1d43c44587 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/session/LoginLogoutReporterTest.java @@ -0,0 +1,74 @@ +/* + * 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.session; + +import static org.mockito.Mockito.verify; +import static org.mockito.Matchers.argThat; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; +import org.mockito.ArgumentMatcher; +import org.mockito.Mockito; + +import junit.framework.TestCase; + +public class LoginLogoutReporterTest extends TestCase +{ + private LoginLogoutReporter _loginLogoutReport; + private Subject _subject = new Subject(); + private LogActor _logActor = Mockito.mock(LogActor.class); + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + _subject.getPrincipals().add(new AuthenticatedPrincipal("mockusername")); + _loginLogoutReport = new LoginLogoutReporter(_logActor, _subject); + } + + public void testLoginLogged() + { + _loginLogoutReport.valueBound(null); + verify(_logActor).message(isLogMessageWithMessage("MNG-1007 : Open : User mockusername")); + } + + public void testLogoutLogged() + { + _loginLogoutReport.valueUnbound(null); + verify(_logActor).message(isLogMessageWithMessage("MNG-1008 : Close : User mockusername")); + } + + private LogMessage isLogMessageWithMessage(final String expectedMessage) + { + return argThat( new ArgumentMatcher<LogMessage>() + { + @Override + public boolean matches(Object argument) + { + LogMessage actual = (LogMessage) argument; + return actual.toString().equals(expectedMessage); + } + }); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ServerConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ServerConfiguration.java index de34ed1fad..d70cec58e0 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ServerConfiguration.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ServerConfiguration.java @@ -605,6 +605,14 @@ public class ServerConfiguration extends ConfigurationPlugin return getBooleanValue("management.http.basic-auth", false); } + /** + * @return value in seconds + */ + public int getHTTPManagementSessionTimeout() + { + return getIntValue("management.http.session-timeout", 60 * 15); + } + public boolean getHTTPSManagementEnabled() { return getBooleanValue("management.https.enabled", false); @@ -1064,4 +1072,5 @@ public class ServerConfiguration extends ConfigurationPlugin } + } diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/ServerConfigurationTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/ServerConfigurationTest.java index 5d4e0edcc9..00b0ad7e39 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/ServerConfigurationTest.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/ServerConfigurationTest.java @@ -313,6 +313,19 @@ public class ServerConfigurationTest extends QpidTestCase assertEquals(false, _serverConfig.getHTTPManagementEnabled()); } + public void testGetHTTPManagementSessionTimeout() throws ConfigurationException + { + // Check default + _serverConfig.initialise(); + assertEquals(60 * 15, _serverConfig.getHTTPManagementSessionTimeout()); + + // Check value we set + _config.setProperty("management.http.session-timeout", 60); + _serverConfig = new ServerConfiguration(_config); + _serverConfig.initialise(); + assertEquals(60, _serverConfig.getHTTPManagementSessionTimeout()); + } + public void testGetHTTPManagementSaslAuthEnabled() throws ConfigurationException { // Check default |
