From 486ee115abb831b2ec78be6777fb1bca9e931df0 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Tue, 25 Oct 2011 17:23:47 -0400
Subject: [PATCH] Documentation. Changed status RPC protection. Status tab for Manager.

---
 docs/01_setup.mkd                                |    2 
 docs/02_rpc.mkd                                  |    8 +
 src/com/gitblit/GitBlit.java                     |    8 
 src/com/gitblit/client/PropertiesTableModel.java |  106 +++++++++++++++++
 src/com/gitblit/wicket/GitBlitWebApp.properties  |    9 +
 src/com/gitblit/client/GitblitClient.java        |    7 +
 src/com/gitblit/client/GitblitPanel.java         |   46 +++++++
 src/com/gitblit/client/StatusPanel.java          |  119 +++++++++++++++++++
 distrib/gitblit.properties                       |    2 
 docs/03_faq.mkd                                  |    9 +
 src/com/gitblit/models/ServerStatus.java         |   11 +
 src/com/gitblit/RpcServlet.java                  |    6 
 12 files changed, 319 insertions(+), 14 deletions(-)

diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties
index 2d48da6..ad1698e 100644
--- a/distrib/gitblit.properties
+++ b/distrib/gitblit.properties
@@ -184,7 +184,7 @@
 # to preemptively replace '/' with '*' or '!' for url string parameters.
 #
 # <https://issues.apache.org/jira/browse/WICKET-1303>
-# <http://tomcat.apache.org/security-6.html>
+# <http://tomcat.apache.org/security-6.html#Fixed_in_Apache_Tomcat_6.0.10>
 #
 # SINCE 0.5.2
 web.forwardSlashCharacter = /
diff --git a/docs/01_setup.mkd b/docs/01_setup.mkd
index b2c5b2f..0939d5a 100644
--- a/docs/01_setup.mkd
+++ b/docs/01_setup.mkd
@@ -159,7 +159,7 @@
 Whitespace is illegal.
 
 #### Passwords
-User passwords are CASE-SENSITIVE and may be *plain* or *md5* formatted (see `gitblit.properties` -> *realm.passwordStorage*).
+User passwords are CASE-SENSITIVE and may be *plain*, *md5*, or *combined-md5* formatted (see `gitblit.properties` -> *realm.passwordStorage*).
 
 #### User Roles
 There are two actual *roles* in Gitblit: *#admin*, which grants administrative powers to that user, and *#notfederated*, which prevents an account from being pulled by another Gitblit instance.  Administrators automatically have access to all repositories.  All other *roles* are repository names.  If a repository is access-restricted, the user must have the repository's name within his/her roles to bypass the access restriction.  This is how users are granted access to a restricted repository.
diff --git a/docs/02_rpc.mkd b/docs/02_rpc.mkd
index 8265508..6384862 100644
--- a/docs/02_rpc.mkd
+++ b/docs/02_rpc.mkd
@@ -14,9 +14,11 @@
 ### RPC Requests
 
 <table>
-<tr><th colspan='2'>url parameters</th><th rowspan='2'>required<br/>permission</th><th colspan='2'>json</th></tr>
+<tr><th colspan='2'>url parameters</th><th rowspan='2'>required<br/>user<br/>permission</th><th colspan='2'>json</th></tr>
 <tr><th>req=</th><th>name=</th><th>post body</th><th>response body</th></tr>
+<tr><td colspan='5'><em>web.enableRpcServlet=true</em></td></tr>
 <tr><td>LIST_REPOSITORIES</td><td>-</td><td>-</td><td>-</td><td>Map&lt;String, RepositoryModel&gt;</td></tr>
+<tr><td colspan='5'><em>web.enableRpcManagement=true</em></td></tr>
 <tr><td>CREATE_REPOSITORY</td><td>repository name</td><td><em>admin</em></td><td>RepositoryModel</td><td>-</td></tr>
 <tr><td>EDIT_REPOSITORY</td><td>repository name</td><td><em>admin</em></td><td>RepositoryModel</td><td>-</td></tr>
 <tr><td>DELETE_REPOSITORY</td><td>repository name</td><td><em>admin</em></td><td>-</td><td>-</td></tr>
@@ -30,6 +32,7 @@
 <tr><td>LIST_FEDERATION_RESULTS</td><td>-</td><td><em>admin</em></td><td>-</td><td>List&lt;FederationModel&gt;</td></tr>
 <tr><td>LIST_FEDERATION_PROPOSALS</td><td>-</td><td><em>admin</em></td><td>-</td><td>List&lt;FederationProposal&gt;</td></tr>
 <tr><td>LIST_FEDERATION_SETS</td><td>-</td><td><em>admin</em></td><td>-</td><td>List&lt;FederationSet&gt;</td></tr>
+<tr><td colspan='5'><em>web.enableRpcAdministration=true</em></td></tr>
 <tr><td>LIST_SETTINGS</td><td>-</td><td><em>admin</em></td><td>-</td><td>ServerSettings (see example below)</td></tr>
 <tr><td>EDIT_SETTINGS</td><td>-</td><td><em>admin</em></td><td>Map&lt;String, String&gt;</td><td>-</td></tr>
 <tr><td>LIST_STATUS</td><td>-</td><td><em>admin</em></td><td>-</td><td>ServerStatus (see example below)</td></tr>
@@ -51,6 +54,9 @@
 [Gitblit Manager](http://code.google.com/p/gitblit/downloads/detail?name=%MANAGER%) is an example Java/Swing application that allows remote administration of a Gitblit server.  
 This application exercises many, but not all, methods from the utility class `com.gitblit.utils.RpcUtils`.
 
+**NOTE:**  
+Gitblit Manager stores your login credentials **INSECURELY** in homedir/.gitblit/config.
+
 ### EGit "Import from Gitblit" Feature (Planning)
 
 One obvious goal of a Gitblit RPC mechanism would be to have an EGit Feature that allows authentication and enumeration of Gitblit repositories from the Eclipse *Import...* menu.  Cloning (hopefully batch) would be delegated to EGit.
diff --git a/docs/03_faq.mkd b/docs/03_faq.mkd
index fdc2020..3e99fb7 100644
--- a/docs/03_faq.mkd
+++ b/docs/03_faq.mkd
@@ -61,6 +61,15 @@
 
 If you are using Apache mod_proxy, specify [AllowEncodedSlashes NoDecode](http://httpd.apache.org/docs/2.2/mod/core.html#allowencodedslashes).
 
+### Running Gitblit on Tomcat
+
+Tomcat takes the extra precaution of [disallowing embedded slashes by default](http://tomcat.apache.org/security-6.html#Fixed_in_Apache_Tomcat_6.0.10).  This breaks Gitblit urls.  
+You have a few options on how to handle this scenario:
+
+1. [Tweak Tomcat](http://tomcat.apache.org/security-6.html#Fixed_in_Apache_Tomcat_6.0.10) 
+2. *web.mountParameters = false* and use non-pretty, parameterized urls
+3. *web.forwardSlashCharacter = !* which tells Gitblit to use **!** instead of **/**
+
 ## General Interest Questions
 
 ### Gitblit?  What kind of name is that?
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 8386d2d..19edf20 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -253,7 +253,7 @@
 	 * @return true if the update succeeded
 	 */
 	public boolean updateSettings(Map<String, String> updatedSettings) {
-		return settings.saveSettings(updatedSettings);		
+		return settings.saveSettings(updatedSettings);
 	}
 
 	public ServerStatus getStatus() {
@@ -1389,7 +1389,7 @@
 		repositoriesFolder = new File(settings.getString(Keys.git.repositoriesFolder, "git"));
 		logger.info("Git repositories folder " + repositoriesFolder.getAbsolutePath());
 		repositoryResolver = new FileResolver<Void>(repositoriesFolder, exportAll);
-		serverStatus = new ServerStatus();
+		serverStatus = new ServerStatus(isGO());
 		String realm = settings.getString(Keys.realm.userService, "users.properties");
 		IUserService loginService = null;
 		try {
@@ -1433,12 +1433,14 @@
 	@Override
 	public void contextInitialized(ServletContextEvent contextEvent) {
 		servletContext = contextEvent.getServletContext();
-		settingsModel = loadSettingModels();
+		settingsModel = loadSettingModels();		
 		if (settings == null) {
 			// Gitblit WAR is running in a servlet container
 			WebXmlSettings webxmlSettings = new WebXmlSettings(contextEvent.getServletContext());
 			configureContext(webxmlSettings, true);
 		}
+		
+		serverStatus.servletContainer = servletContext.getServerInfo();
 	}
 
 	/**
diff --git a/src/com/gitblit/RpcServlet.java b/src/com/gitblit/RpcServlet.java
index 1136692..dd99e3f 100644
--- a/src/com/gitblit/RpcServlet.java
+++ b/src/com/gitblit/RpcServlet.java
@@ -199,7 +199,11 @@
 			}
 		} else if (RpcRequest.LIST_STATUS.equals(reqType)) {
 			// return the server's status information
-			result = GitBlit.self().getStatus();
+			if (GitBlit.getBoolean(Keys.web.enableRpcAdministration, false)) {
+				result = GitBlit.self().getStatus();
+			} else {
+				response.sendError(notAllowedCode);
+			}
 		}
 
 		// send the result of the request
diff --git a/src/com/gitblit/client/GitblitClient.java b/src/com/gitblit/client/GitblitClient.java
index d3a92b0..761283e 100644
--- a/src/com/gitblit/client/GitblitClient.java
+++ b/src/com/gitblit/client/GitblitClient.java
@@ -87,7 +87,7 @@
 
 		try {
 			refreshSettings();
-			status = RpcUtils.getStatus(url, account, password);
+			refreshStatus();
 			allowAdministration = true;
 		} catch (UnauthorizedException e) {
 		} catch (ForbiddenException e) {
@@ -141,6 +141,11 @@
 		settings = RpcUtils.getSettings(url, account, password);
 		return settings;
 	}
+	
+	public ServerStatus refreshStatus() throws IOException {
+		status = RpcUtils.getStatus(url, account, password);
+		return status;
+	}
 
 	public List<FederationModel> refreshFederationRegistrations() throws IOException {
 		List<FederationModel> list = RpcUtils.getFederationRegistrations(url, account, password);
diff --git a/src/com/gitblit/client/GitblitPanel.java b/src/com/gitblit/client/GitblitPanel.java
index d67921b..1a24f71 100644
--- a/src/com/gitblit/client/GitblitPanel.java
+++ b/src/com/gitblit/client/GitblitPanel.java
@@ -118,6 +118,8 @@
 
 	private HeaderPanel settingsHeader;
 
+	private StatusPanel statusPanel;
+
 	public GitblitPanel(GitblitRegistration reg) {
 		this(reg.url, reg.account, reg.password);
 	}
@@ -129,6 +131,7 @@
 		tabs.addTab(Translation.get("gb.repositories"), createRepositoriesPanel());
 		tabs.addTab(Translation.get("gb.users"), createUsersPanel());
 		tabs.addTab(Translation.get("gb.settings"), createSettingsPanel());
+		tabs.addTab(Translation.get("gb.status"), createStatusPanel());
 
 		setLayout(new BorderLayout());
 		add(tabs, BorderLayout.CENTER);
@@ -482,6 +485,24 @@
 		return settingsPanel;
 	}
 
+	private JPanel createStatusPanel() {
+		JButton refreshStatus = new JButton(Translation.get("gb.refresh"));
+		refreshStatus.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				refreshStatus();
+			}
+		});
+
+		JPanel controls = new JPanel();
+		controls.add(refreshStatus);
+
+		JPanel panel = new JPanel(new BorderLayout());
+		statusPanel = new StatusPanel();
+		panel.add(statusPanel, BorderLayout.CENTER);
+		panel.add(controls, BorderLayout.SOUTH);
+		return panel;
+	}
+
 	public void login() throws IOException {
 		gitblit.login();
 
@@ -505,6 +526,7 @@
 
 		if (gitblit.allowAdministration()) {
 			updateSettingsTable();
+			updateStatusPanel();
 			Utils.packColumns(settingsTable, 5);
 		} else {
 			// remove the settings tab
@@ -537,6 +559,10 @@
 		settingsModel.setSettings(gitblit.getSettings());
 		settingsModel.fireTableDataChanged();
 		settingsHeader.setText(Translation.get("gb.settings"));
+	}
+
+	private void updateStatusPanel() {
+		statusPanel.setStatus(gitblit.getStatus());
 	}
 
 	private void filterRepositories(final String fragment) {
@@ -934,6 +960,22 @@
 		worker.execute();
 	}
 
+	protected void refreshStatus() {
+		GitblitWorker worker = new GitblitWorker(GitblitPanel.this, RpcRequest.LIST_STATUS) {
+			@Override
+			protected Boolean doRequest() throws IOException {
+				gitblit.refreshStatus();
+				return true;
+			}
+
+			@Override
+			protected void onSuccess() {
+				updateStatusPanel();
+			}
+		};
+		worker.execute();
+	}
+
 	protected void editSetting(final SettingModel settingModel) {
 		final JTextField textField = new JTextField(settingModel.currentValue);
 		JPanel editPanel = new JPanel(new GridLayout(0, 1));
@@ -949,8 +991,8 @@
 		if (settingModel.currentValue.equals(settingModel.defaultValue)) {
 			options = new String[] { Translation.get("gb.cancel"), Translation.get("gb.save") };
 		} else {
-			options = new String[] { Translation.get("gb.cancel"), Translation.get("gb.setDefault"),
-					Translation.get("gb.save") };
+			options = new String[] { Translation.get("gb.cancel"),
+					Translation.get("gb.setDefault"), Translation.get("gb.save") };
 		}
 		String defaultOption = options[0];
 		int selection = JOptionPane.showOptionDialog(GitblitPanel.this, settingPanel,
diff --git a/src/com/gitblit/client/PropertiesTableModel.java b/src/com/gitblit/client/PropertiesTableModel.java
new file mode 100644
index 0000000..0c803f4
--- /dev/null
+++ b/src/com/gitblit/client/PropertiesTableModel.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed 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 com.gitblit.client;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * Table model of a map of properties.
+ * 
+ * @author James Moger
+ * 
+ */
+public class PropertiesTableModel extends AbstractTableModel {
+
+	private static final long serialVersionUID = 1L;
+
+	List<String> keys;
+
+	Map<String, String> map;
+
+	enum Columns {
+		Name, Value;
+
+		@Override
+		public String toString() {
+			return name().replace('_', ' ');
+		}
+	}
+
+	public PropertiesTableModel() {
+		this(new HashMap<String, String>());
+	}
+
+	public PropertiesTableModel(Map<String, String> map) {
+		setProperties(map);
+	}
+
+	public void setProperties(Map<String, String> map) {
+		this.map = map;
+		keys = new ArrayList<String>(map.keySet());
+		Collections.sort(this.keys);
+	}
+
+	@Override
+	public int getRowCount() {
+		return keys.size();
+	}
+
+	@Override
+	public int getColumnCount() {
+		return Columns.values().length;
+	}
+
+	@Override
+	public String getColumnName(int column) {
+		Columns col = Columns.values()[column];
+		switch (col) {
+		case Name:
+			return Translation.get("gb.name");
+		}
+		return "";
+	}
+
+	/**
+	 * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
+	 * 
+	 * @param columnIndex
+	 *            the column being queried
+	 * @return the Object.class
+	 */
+	public Class<?> getColumnClass(int columnIndex) {
+		return String.class;
+	}
+
+	@Override
+	public Object getValueAt(int rowIndex, int columnIndex) {
+		String key = keys.get(rowIndex);
+		Columns col = Columns.values()[columnIndex];
+		switch (col) {
+		case Name:
+			return key;
+		case Value:
+			return map.get(key);
+		}
+		return null;
+	}
+}
diff --git a/src/com/gitblit/client/StatusPanel.java b/src/com/gitblit/client/StatusPanel.java
new file mode 100644
index 0000000..9015c80
--- /dev/null
+++ b/src/com/gitblit/client/StatusPanel.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed 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 com.gitblit.client;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.Insets;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+
+import com.gitblit.models.ServerStatus;
+import com.gitblit.utils.ByteFormat;
+
+/**
+ * This panel displays the server status.
+ * 
+ * @author James Moger
+ */
+public class StatusPanel extends JPanel {
+
+	private static final long serialVersionUID = 1L;
+	private final Insets insets = new Insets(5, 5, 5, 5);
+	private JLabel bootDate;
+	private JLabel servletContainer;
+	private JLabel heapMaximum;
+	private JLabel heapAllocated;
+	private JLabel heapUsed;
+	private PropertiesTableModel model;
+	private HeaderPanel headerPanel;
+
+	public StatusPanel() {
+		super();
+		initialize();
+	}
+
+	public StatusPanel(ServerStatus status) {
+		this();
+		setStatus(status);
+	}
+
+	private void initialize() {
+		bootDate = new JLabel();
+		servletContainer = new JLabel();
+
+		heapMaximum = new JLabel();
+		heapAllocated = new JLabel();
+		heapUsed = new JLabel();
+
+		JPanel fieldsPanel = new JPanel(new GridLayout(0, 1));
+		fieldsPanel.add(createFieldPanel("gb.bootDate", bootDate));
+		fieldsPanel.add(createFieldPanel("gb.servletContainer", servletContainer));
+		fieldsPanel.add(createFieldPanel("gb.heapUsed", heapUsed));
+		fieldsPanel.add(createFieldPanel("gb.heapAllocated", heapAllocated));
+		fieldsPanel.add(createFieldPanel("gb.heapMaximum", heapMaximum));
+
+		model = new PropertiesTableModel();
+		JTable propertiesTable = Utils.newTable(model);
+		String name = propertiesTable.getColumnName(PropertiesTableModel.Columns.Name.ordinal());
+		NameRenderer nameRenderer = new NameRenderer();
+		propertiesTable.setRowHeight(nameRenderer.getFont().getSize() + 8);
+		propertiesTable.getColumn(name).setCellRenderer(nameRenderer);
+
+		JPanel centerPanel = new JPanel(new BorderLayout());
+		centerPanel.add(fieldsPanel, BorderLayout.NORTH);
+		centerPanel.add(new JScrollPane(propertiesTable), BorderLayout.CENTER);
+
+		headerPanel = new HeaderPanel(Translation.get("gb.status"), null);
+		setLayout(new BorderLayout());
+		add(headerPanel, BorderLayout.NORTH);
+		add(centerPanel, BorderLayout.CENTER);
+	}
+
+	private JPanel createFieldPanel(String key, JLabel valueLabel) {
+		JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5));
+		JLabel textLabel = new JLabel(Translation.get(key));
+		textLabel.setFont(textLabel.getFont().deriveFont(Font.BOLD));
+		textLabel.setPreferredSize(new Dimension(120, valueLabel.getFont().getSize() + 4));
+		panel.add(textLabel);
+		panel.add(valueLabel);
+		return panel;
+	}
+
+	@Override
+	public Insets getInsets() {
+		return insets;
+	}
+
+	public void setStatus(ServerStatus status) {
+		headerPanel.setText(Translation.get("gb.status"));
+		bootDate.setText(status.bootDate.toString());
+		servletContainer.setText(status.servletContainer);
+		ByteFormat byteFormat = new ByteFormat();
+		heapMaximum.setText(byteFormat.format(status.heapMaximum));
+		heapAllocated.setText(byteFormat.format(status.heapAllocated));
+		heapUsed.setText(byteFormat.format(status.heapAllocated - status.heapFree) + " ("
+				+ byteFormat.format(status.heapFree) + " " + Translation.get("gb.free") + ")");
+		model.setProperties(status.systemProperties);
+		model.fireTableDataChanged();
+	}
+}
diff --git a/src/com/gitblit/models/ServerStatus.java b/src/com/gitblit/models/ServerStatus.java
index 8dfc0fb..1c40b5f 100644
--- a/src/com/gitblit/models/ServerStatus.java
+++ b/src/com/gitblit/models/ServerStatus.java
@@ -33,18 +33,23 @@
 
 	public final Date bootDate;
 	
+	public final boolean isGO;
+	
 	public final Map<String, String> systemProperties;
 
-	public final long heapSize;
+	public final long heapMaximum;
 
 	public volatile long heapAllocated;
 	
 	public volatile long heapFree;
+	
+	public String servletContainer;
 
-	public ServerStatus() {
+	public ServerStatus(boolean isGO) {
 		bootDate = new Date();
+		this.isGO = isGO;
 		
-		heapSize = Runtime.getRuntime().maxMemory();
+		heapMaximum = Runtime.getRuntime().maxMemory();
 		
 		systemProperties = new TreeMap<String, String>();
 		put("file.encoding");
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index 2052fb1..715cb15 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -166,4 +166,11 @@
 gb.accessLevel = access level
 gb.default = default
 gb.setDefault = set default
-gb.since = since
\ No newline at end of file
+gb.since = since
+gb.status = status
+gb.bootDate = boot date
+gb.servletContainer = servlet container
+gb.heapMaximum = maximum heap
+gb.heapAllocated = allocated heap
+gb.heapUsed = used heap
+gb.free = free
\ No newline at end of file

--
Gitblit v1.9.1