Return-Path: X-Original-To: apmail-accumulo-commits-archive@www.apache.org Delivered-To: apmail-accumulo-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 9DB0811C7C for ; Sat, 24 May 2014 00:31:17 +0000 (UTC) Received: (qmail 45578 invoked by uid 500); 24 May 2014 00:31:17 -0000 Delivered-To: apmail-accumulo-commits-archive@accumulo.apache.org Received: (qmail 45555 invoked by uid 500); 24 May 2014 00:31:17 -0000 Mailing-List: contact commits-help@accumulo.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@accumulo.apache.org Delivered-To: mailing list commits@accumulo.apache.org Received: (qmail 45548 invoked by uid 99); 24 May 2014 00:31:17 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 24 May 2014 00:31:17 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 1DDB69A3861; Sat, 24 May 2014 00:31:16 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: elserj@apache.org To: commits@accumulo.apache.org Date: Sat, 24 May 2014 00:31:19 -0000 Message-Id: <44db62d9b77344ab9603b164b5abd258@git.apache.org> In-Reply-To: <971e3b4c2af347baa9f987e918a5bf9b@git.apache.org> References: <971e3b4c2af347baa9f987e918a5bf9b@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [4/7] git commit: Merge branch '1.5.2-SNAPSHOT' into 1.6.1-SNAPSHOT Merge branch '1.5.2-SNAPSHOT' into 1.6.1-SNAPSHOT Conflicts: server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ShellServlet.java Project: http://git-wip-us.apache.org/repos/asf/accumulo/repo Commit: http://git-wip-us.apache.org/repos/asf/accumulo/commit/eb6b3253 Tree: http://git-wip-us.apache.org/repos/asf/accumulo/tree/eb6b3253 Diff: http://git-wip-us.apache.org/repos/asf/accumulo/diff/eb6b3253 Branch: refs/heads/master Commit: eb6b3253b7df7c7e3c4b8ed7df055d951c0e5621 Parents: eb7aac3 5d4cf3b Author: Josh Elser Authored: Fri May 23 20:24:18 2014 -0400 Committer: Josh Elser Committed: Fri May 23 20:24:18 2014 -0400 ---------------------------------------------------------------------- .../accumulo/monitor/servlets/ShellServlet.java | 43 +++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/accumulo/blob/eb6b3253/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ShellServlet.java ---------------------------------------------------------------------- diff --cc server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ShellServlet.java index 03f6831,0000000..665b132 mode 100644,000000..100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ShellServlet.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/servlets/ShellServlet.java @@@ -1,333 -1,0 +1,356 @@@ +/* + * 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.accumulo.monitor.servlets; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; ++import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import jline.console.ConsoleReader; + +import org.apache.accumulo.core.Constants; +import org.apache.accumulo.core.util.shell.Shell; + +public class ShellServlet extends BasicServlet { + private static final long serialVersionUID = 1L; + private Map userShells = new HashMap(); + private ExecutorService service = Executors.newCachedThreadPool(); - ++ ++ public static final String CSRF_KEY = "csrf_token"; ++ + @Override + protected String getTitle(HttpServletRequest req) { + return "Shell"; + } + + @Override + protected void pageBody(HttpServletRequest req, HttpServletResponse response, StringBuilder sb) throws IOException { + HttpSession session = req.getSession(true); ++ final String CSRF_TOKEN; ++ if (null == session.getAttribute(CSRF_KEY)) { ++ // No token, make one ++ CSRF_TOKEN = UUID.randomUUID().toString(); ++ session.setAttribute(CSRF_KEY, CSRF_TOKEN); ++ } else { ++ // Pull the token out of the session ++ CSRF_TOKEN = (String) session.getAttribute(CSRF_KEY); ++ if (null == CSRF_TOKEN) { ++ throw new RuntimeException("No valid CSRF token exists in session"); ++ } ++ } ++ + String user = (String) session.getAttribute("user"); + if (user == null) { + // user attribute is null, check to see if username and password are passed as parameters + user = req.getParameter("user"); + String pass = req.getParameter("pass"); + String mock = req.getParameter("mock"); + if (user == null || pass == null) { + // username or password are null, re-authenticate - sb.append(authenticationForm(req.getRequestURI())); ++ sb.append(authenticationForm(req.getRequestURI(), CSRF_TOKEN)); + return; + } + try { + // get a new shell for this user + ShellExecutionThread shellThread = new ShellExecutionThread(user, pass, mock); + service.submit(shellThread); + userShells.put(session.getId(), shellThread); + } catch (IOException e) { + // error validating user, reauthenticate - sb.append("
Invalid user/password
" + authenticationForm(req.getRequestURI())); ++ sb.append("
Invalid user/password
" + authenticationForm(req.getRequestURI(), CSRF_TOKEN)); + return; + } + session.setAttribute("user", user); + } + if (!userShells.containsKey(session.getId())) { + // no existing shell for this user, re-authenticate - sb.append(authenticationForm(req.getRequestURI())); ++ sb.append(authenticationForm(req.getRequestURI(), UUID.randomUUID().toString())); + return; + } ++ + ShellExecutionThread shellThread = userShells.get(session.getId()); + shellThread.getOutput(); + shellThread.printInfo(); + sb.append("
\n"); + sb.append("
").append(shellThread.getOutput()).append("
\n"); + sb.append("
").append(shellThread.getPrompt()); + sb.append("\n"); + sb.append("
\n
\n"); + sb.append("\n"); + sb.append("\n"); + } - ++ + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - HttpSession session = req.getSession(true); ++ final HttpSession session = req.getSession(true); + String user = (String) session.getAttribute("user"); + if (user == null || !userShells.containsKey(session.getId())) { + // no existing shell for user, re-authenticate + doGet(req, resp); + return; + } ++ final String CSRF_TOKEN = (String) session.getAttribute(CSRF_KEY); ++ if (null == CSRF_TOKEN) { ++ // no csrf token, need to re-auth ++ doGet(req, resp); ++ } + ShellExecutionThread shellThread = userShells.get(session.getId()); + String cmd = req.getParameter("cmd"); + if (cmd == null) { + // the command is null, just print prompt + resp.getWriter().append(shellThread.getPrompt()); + resp.getWriter().flush(); + return; + } + shellThread.addInputString(cmd); + shellThread.waitUntilReady(); + if (shellThread.isDone()) { + // the command was exit, invalidate session + userShells.remove(session.getId()); + session.invalidate(); + return; + } + // get the shell's output + StringBuilder sb = new StringBuilder(); + sb.append(shellThread.getOutput().replace("<", "<").replace(">", ">")); + if (sb.length() == 0 || !(sb.charAt(sb.length() - 1) == '\n')) + sb.append("\n"); + // check if shell is waiting for input + if (!shellThread.isWaitingForInput()) + sb.append(shellThread.getPrompt()); + // check if shell is waiting for password input + if (shellThread.isMasking()) + sb.append("*"); + resp.getWriter().append(sb.toString()); + resp.getWriter().flush(); + } - - private String authenticationForm(String requestURI) { ++ ++ private String authenticationForm(String requestURI, String csrfToken) { + return "
" + + "" + + "" - + "
Mock: 
Username: 
Password: 
"; ++ + "Password: " ++ + ""; + } + + private static class StringBuilderOutputStream extends OutputStream { + StringBuilder sb = new StringBuilder(); + + @Override + public void write(int b) throws IOException { + sb.append((char) (0xff & b)); + } + + public String get() { + return sb.toString(); + } + + public void clear() { + sb.setLength(0); + } + } + + private static class ShellExecutionThread extends InputStream implements Runnable { + private Shell shell; + StringBuilderOutputStream output; + private String cmd; + private int cmdIndex; + private boolean done; + private boolean readWait; + + private ShellExecutionThread(String username, String password, String mock) throws IOException { + this.done = false; + this.cmd = null; + this.cmdIndex = 0; + this.readWait = false; + this.output = new StringBuilderOutputStream(); + ConsoleReader reader = new ConsoleReader(this, output); + this.shell = new Shell(reader, new PrintWriter(new OutputStreamWriter(output, Constants.UTF8))); + shell.setLogErrorsToConsole(); + if (mock != null) { + if (shell.config("--fake", "-u", username, "-p", password)) + throw new IOException("mock shell config error"); + } else if (shell.config("-u", username, "-p", password)) { + throw new IOException("shell config error"); + } + } + + @Override + public synchronized int read() throws IOException { + if (cmd == null) { + readWait = true; + this.notifyAll(); + } + while (cmd == null) { + try { + this.wait(); + } catch (InterruptedException e) {} + } + readWait = false; + int c; + if (cmdIndex == cmd.length()) + c = '\n'; + else + c = cmd.charAt(cmdIndex); + cmdIndex++; + if (cmdIndex > cmd.length()) { + cmd = null; + cmdIndex = 0; + this.notifyAll(); + } + return c; + } + + @Override + public synchronized void run() { + Thread.currentThread().setName("shell thread"); + while (!shell.hasExited()) { + while (cmd == null) { + try { + this.wait(); + } catch (InterruptedException e) {} + } + String tcmd = cmd; + cmd = null; + cmdIndex = 0; + try { + shell.execCommand(tcmd, false, true); + } catch (IOException e) {} + this.notifyAll(); + } + done = true; + this.notifyAll(); + } + + public synchronized void addInputString(String s) { + if (done) + throw new IllegalStateException("adding string to exited shell"); + if (cmd == null) { + cmd = s; + } else { + throw new IllegalStateException("adding string to shell not waiting for input"); + } + this.notifyAll(); + } + + public synchronized void waitUntilReady() { + while (cmd != null) { + try { + this.wait(); + } catch (InterruptedException e) {} + } + } + + public synchronized String getOutput() { + String s = output.get(); + output.clear(); + return s; + } + + public String getPrompt() { + return shell.getDefaultPrompt(); + } + + public void printInfo() throws IOException { + shell.printInfo(); + } + + public boolean isMasking() { + return shell.isMasking(); + } + + public synchronized boolean isWaitingForInput() { + return readWait; + } + + public boolean isDone() { + return done; + } + } +}