summaryrefslogtreecommitdiffstats
path: root/src/main/java/com/gitblit/authority
diff options
context:
space:
mode:
authorJames Moger <james.moger@gitblit.com>2013-03-27 12:46:05 -0400
committerJames Moger <james.moger@gitblit.com>2013-03-27 17:22:08 -0400
commitf6b200be4c8b90c26886c6cdd5809abac8c4ac15 (patch)
treea948dbcf6f24bf884ad95a8d6830b4ec4e1706cf /src/main/java/com/gitblit/authority
parentb79ade104858ce6714a7329b7629b331564a2ea5 (diff)
downloadgitblit-f6b200be4c8b90c26886c6cdd5809abac8c4ac15.tar.gz
gitblit-f6b200be4c8b90c26886c6cdd5809abac8c4ac15.zip
Reorganized to Apache Standard Directory Layout & integrated Moxie
This is a massive commit which reorganizes the entire project structure (although it is still monolithic), removes the Build classes, and switches to Moxie, a smarter Ant build tookit based on the original Gitblit Build classes. The Ant build script will likely require additional fine-tuning, but this is big step forward.
Diffstat (limited to 'src/main/java/com/gitblit/authority')
-rw-r--r--src/main/java/com/gitblit/authority/AuthorityWorker.java58
-rw-r--r--src/main/java/com/gitblit/authority/CertificateStatus.java20
-rw-r--r--src/main/java/com/gitblit/authority/CertificateStatusRenderer.java82
-rw-r--r--src/main/java/com/gitblit/authority/CertificatesTableModel.java166
-rw-r--r--src/main/java/com/gitblit/authority/DefaultOidsPanel.java80
-rw-r--r--src/main/java/com/gitblit/authority/GitblitAuthority.java921
-rw-r--r--src/main/java/com/gitblit/authority/Launcher.java165
-rw-r--r--src/main/java/com/gitblit/authority/NewCertificateConfig.java93
-rw-r--r--src/main/java/com/gitblit/authority/NewClientCertificateDialog.java177
-rw-r--r--src/main/java/com/gitblit/authority/NewSSLCertificateDialog.java140
-rw-r--r--src/main/java/com/gitblit/authority/RequestFocusListener.java79
-rw-r--r--src/main/java/com/gitblit/authority/UserCertificateConfig.java70
-rw-r--r--src/main/java/com/gitblit/authority/UserCertificateModel.java151
-rw-r--r--src/main/java/com/gitblit/authority/UserCertificatePanel.java298
-rw-r--r--src/main/java/com/gitblit/authority/UserCertificateTableModel.java131
-rw-r--r--src/main/java/com/gitblit/authority/UserOidsPanel.java95
-rw-r--r--src/main/java/com/gitblit/authority/Utils.java123
-rw-r--r--src/main/java/com/gitblit/authority/X509CertificateViewer.java129
18 files changed, 2978 insertions, 0 deletions
diff --git a/src/main/java/com/gitblit/authority/AuthorityWorker.java b/src/main/java/com/gitblit/authority/AuthorityWorker.java
new file mode 100644
index 00000000..262bbb53
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/AuthorityWorker.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 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.authority;
+
+import java.awt.Component;
+import java.awt.Cursor;
+import java.io.IOException;
+
+import javax.swing.SwingWorker;
+
+public abstract class AuthorityWorker extends SwingWorker<Boolean, Void> {
+
+ private final Component parent;
+
+ public AuthorityWorker(Component parent) {
+ this.parent = parent;
+ parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ }
+
+ @Override
+ protected Boolean doInBackground() throws IOException {
+ return doRequest();
+ }
+
+ protected void done() {
+ parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ try {
+ Boolean success = get();
+ if (success) {
+ onSuccess();
+ } else {
+ onFailure();
+ }
+ } catch (Throwable t) {
+ Utils.showException(parent, t);
+ }
+ }
+
+ protected abstract Boolean doRequest() throws IOException;
+
+ protected abstract void onSuccess();
+
+ protected void onFailure() {
+ }
+}
diff --git a/src/main/java/com/gitblit/authority/CertificateStatus.java b/src/main/java/com/gitblit/authority/CertificateStatus.java
new file mode 100644
index 00000000..79c51628
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/CertificateStatus.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2012 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.authority;
+
+public enum CertificateStatus {
+ unknown, ok, expiring, expired, revoked
+}
diff --git a/src/main/java/com/gitblit/authority/CertificateStatusRenderer.java b/src/main/java/com/gitblit/authority/CertificateStatusRenderer.java
new file mode 100644
index 00000000..7a708ea4
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/CertificateStatusRenderer.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012 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.authority;
+
+import java.awt.Component;
+
+import javax.swing.ImageIcon;
+import javax.swing.JTable;
+import javax.swing.table.DefaultTableCellRenderer;
+
+import com.gitblit.client.Translation;
+
+/**
+ * Displays a subscribed icon on the left of the repository name, if there is at
+ * least one subscribed branch.
+ *
+ * @author James Moger
+ *
+ */
+public class CertificateStatusRenderer extends DefaultTableCellRenderer {
+
+ private static final long serialVersionUID = 1L;
+
+ private final ImageIcon unknownIcon;
+ private final ImageIcon revokedIcon;
+ private final ImageIcon expiredIcon;
+ private final ImageIcon expiringIcon;
+ private final ImageIcon okIcon;
+
+ public CertificateStatusRenderer() {
+ super();
+ unknownIcon = new ImageIcon(getClass().getResource("/bullet_white.png"));
+ revokedIcon = new ImageIcon(getClass().getResource("/bullet_delete.png"));
+ expiredIcon = new ImageIcon(getClass().getResource("/bullet_red.png"));
+ expiringIcon = new ImageIcon(getClass().getResource("/bullet_orange.png"));
+ okIcon = new ImageIcon(getClass().getResource("/bullet_green.png"));
+ }
+
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+ boolean hasFocus, int row, int column) {
+ super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+ if (value instanceof CertificateStatus) {
+ CertificateStatus status = (CertificateStatus) value;
+ switch(status) {
+ case revoked:
+ setText(Translation.get("gb.revoked"));
+ setIcon(revokedIcon);
+ break;
+ case expiring:
+ setText(Translation.get("gb.expiring"));
+ setIcon(expiringIcon);
+ break;
+ case expired:
+ setText(Translation.get("gb.expired"));
+ setIcon(expiredIcon);
+ break;
+ case unknown:
+ setText("");
+ setIcon(unknownIcon);
+ break;
+ default:
+ setText(Translation.get("gb.ok"));
+ setIcon(okIcon);
+ break;
+ }
+ }
+ return this;
+ }
+}
diff --git a/src/main/java/com/gitblit/authority/CertificatesTableModel.java b/src/main/java/com/gitblit/authority/CertificatesTableModel.java
new file mode 100644
index 00000000..44d80e3a
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/CertificatesTableModel.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2012 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.authority;
+
+import java.math.BigInteger;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+
+import javax.swing.table.AbstractTableModel;
+
+import com.gitblit.client.Translation;
+import com.gitblit.utils.X509Utils.RevocationReason;
+
+/**
+ * Table model of a list of user certificate models.
+ *
+ * @author James Moger
+ *
+ */
+public class CertificatesTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+
+ UserCertificateModel ucm;
+
+ enum Columns {
+ SerialNumber, Status, Reason, Issued, Expires;
+
+ @Override
+ public String toString() {
+ return name().replace('_', ' ');
+ }
+ }
+
+ public CertificatesTableModel() {
+ }
+
+ @Override
+ public int getRowCount() {
+ return ucm == null || ucm.certs == null ? 0 : ucm.certs.size();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return Columns.values().length;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ Columns col = Columns.values()[column];
+ switch (col) {
+ case SerialNumber:
+ return Translation.get("gb.serialNumber");
+ case Issued:
+ return Translation.get("gb.issued");
+ case Expires:
+ return Translation.get("gb.expires");
+ case Status:
+ return Translation.get("gb.status");
+ case Reason:
+ return Translation.get("gb.reason");
+ }
+ 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) {
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ case Status:
+ return CertificateStatus.class;
+ case Issued:
+ return Date.class;
+ case Expires:
+ return Date.class;
+ case SerialNumber:
+ return BigInteger.class;
+ default:
+ return String.class;
+ }
+ }
+
+ @Override
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ X509Certificate cert = ucm.certs.get(rowIndex);
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ case Status:
+ return ucm.getStatus(cert);
+ case SerialNumber:
+ return cert.getSerialNumber();
+ case Issued:
+ return cert.getNotBefore();
+ case Expires:
+ return cert.getNotAfter();
+ case Reason:
+ if (ucm.getStatus(cert).equals(CertificateStatus.revoked)) {
+ RevocationReason r = ucm.getRevocationReason(cert.getSerialNumber());
+ return Translation.get("gb." + r.name());
+ }
+ }
+ return null;
+ }
+
+ public X509Certificate get(int modelRow) {
+ return ucm.certs.get(modelRow);
+ }
+
+ public void setUserCertificateModel(UserCertificateModel ucm) {
+ this.ucm = ucm;
+ Collections.sort(ucm.certs, new Comparator<X509Certificate>() {
+ @Override
+ public int compare(X509Certificate o1, X509Certificate o2) {
+ // sort by issue date in reverse chronological order
+ int result = o2.getNotBefore().compareTo(o1.getNotBefore());
+ if (result == 0) {
+ // same issue date, show expiring first
+ boolean r1 = CertificatesTableModel.this.ucm.isRevoked(o1.getSerialNumber());
+ boolean r2 = CertificatesTableModel.this.ucm.isRevoked(o2.getSerialNumber());
+ if ((r1 && r2) || (!r1 && !r2)) {
+ // both revoked or both not revoked
+ // chronlogical order by expiration dates
+ result = o1.getNotAfter().compareTo(o2.getNotAfter());
+ } else if (r1) {
+ // r1 is revoked, r2 first
+ return 1;
+ } else {
+ // r2 is revoked, r1 first
+ return -1;
+ }
+ }
+ return result;
+ }
+ });
+ }
+}
diff --git a/src/main/java/com/gitblit/authority/DefaultOidsPanel.java b/src/main/java/com/gitblit/authority/DefaultOidsPanel.java
new file mode 100644
index 00000000..12b919fa
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/DefaultOidsPanel.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012 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.authority;
+
+import java.awt.GridLayout;
+
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import com.gitblit.client.Translation;
+import com.gitblit.utils.X509Utils.X509Metadata;
+
+public class DefaultOidsPanel extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private JTextField organizationalUnit;
+ private JTextField organization;
+ private JTextField locality;
+ private JTextField stateProvince;
+ private JTextField countryCode;
+
+ public DefaultOidsPanel(X509Metadata metadata) {
+ super();
+
+ organizationalUnit = new JTextField(metadata.getOID("OU", ""), 20);
+ organization = new JTextField(metadata.getOID("O", ""), 20);
+ locality = new JTextField(metadata.getOID("L", ""), 20);
+ stateProvince = new JTextField(metadata.getOID("ST", ""), 20);
+ countryCode = new JTextField(metadata.getOID("C", ""), 20);
+
+ setLayout(new GridLayout(0, 1, Utils.MARGIN, Utils.MARGIN));
+ add(Utils.newFieldPanel(Translation.get("gb.organizationalUnit") + " (OU)", organizationalUnit));
+ add(Utils.newFieldPanel(Translation.get("gb.organization") + " (O)", organization));
+ add(Utils.newFieldPanel(Translation.get("gb.locality") + " (L)", locality));
+ add(Utils.newFieldPanel(Translation.get("gb.stateProvince") + " (ST)", stateProvince));
+ add(Utils.newFieldPanel(Translation.get("gb.countryCode") + " (C)", countryCode));
+ }
+
+ public void update(X509Metadata metadata) {
+ metadata.setOID("OU", organizationalUnit.getText());
+ metadata.setOID("O", organization.getText());
+ metadata.setOID("L", locality.getText());
+ metadata.setOID("ST", stateProvince.getText());
+ metadata.setOID("C", countryCode.getText());
+ }
+
+ public String getOrganizationalUnit() {
+ return organizationalUnit.getText();
+ }
+
+ public String getOrganization() {
+ return organization.getText();
+ }
+
+ public String getLocality() {
+ return locality.getText();
+ }
+
+ public String getStateProvince() {
+ return stateProvince.getText();
+ }
+
+ public String getCountryCode() {
+ return countryCode.getText();
+ }
+}
diff --git a/src/main/java/com/gitblit/authority/GitblitAuthority.java b/src/main/java/com/gitblit/authority/GitblitAuthority.java
new file mode 100644
index 00000000..1a1f96db
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/GitblitAuthority.java
@@ -0,0 +1,921 @@
+/*
+ * Copyright 2012 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.authority;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Desktop;
+import java.awt.Dimension;
+import java.awt.EventQueue;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.BufferedInputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.net.URI;
+import java.security.PrivateKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.activation.DataHandler;
+import javax.activation.FileDataSource;
+import javax.mail.Message;
+import javax.mail.Multipart;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMultipart;
+import javax.swing.ImageIcon;
+import javax.swing.InputVerifier;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.JToolBar;
+import javax.swing.RowFilter;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableRowSorter;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.ConfigUserService;
+import com.gitblit.Constants;
+import com.gitblit.FileSettings;
+import com.gitblit.IStoredSettings;
+import com.gitblit.IUserService;
+import com.gitblit.Keys;
+import com.gitblit.MailExecutor;
+import com.gitblit.client.HeaderPanel;
+import com.gitblit.client.Translation;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.FileUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.utils.X509Utils;
+import com.gitblit.utils.X509Utils.RevocationReason;
+import com.gitblit.utils.X509Utils.X509Log;
+import com.gitblit.utils.X509Utils.X509Metadata;
+
+/**
+ * Simple GUI tool for administering Gitblit client certificates.
+ *
+ * @author James Moger
+ *
+ */
+public class GitblitAuthority extends JFrame implements X509Log {
+
+ private static final long serialVersionUID = 1L;
+
+ private final UserCertificateTableModel tableModel;
+
+ private UserCertificatePanel userCertificatePanel;
+
+ private File folder;
+
+ private IStoredSettings gitblitSettings;
+
+ private IUserService userService;
+
+ private String caKeystorePassword;
+
+ private JTable table;
+
+ private int defaultDuration;
+
+ private TableRowSorter<UserCertificateTableModel> defaultSorter;
+
+ private MailExecutor mail;
+
+ private JButton certificateDefaultsButton;
+
+ private JButton newSSLCertificate;
+
+ public static void main(String... args) {
+ // filter out the baseFolder parameter
+ String folder = "data";
+ for (int i = 0; i< args.length; i++) {
+ String arg = args[i];
+ if (arg.equals("--baseFolder")) {
+ if (i + 1 == args.length) {
+ System.out.println("Invalid --baseFolder parameter!");
+ System.exit(-1);
+ } else if (args[i + 1] != ".") {
+ folder = args[i+1];
+ }
+ break;
+ }
+ }
+ final String baseFolder = folder;
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (Exception e) {
+ }
+ GitblitAuthority authority = new GitblitAuthority();
+ authority.initialize(baseFolder);
+ authority.setLocationRelativeTo(null);
+ authority.setVisible(true);
+ }
+ });
+ }
+
+ public GitblitAuthority() {
+ super();
+ tableModel = new UserCertificateTableModel();
+ defaultSorter = new TableRowSorter<UserCertificateTableModel>(tableModel);
+ }
+
+ public void initialize(String baseFolder) {
+ setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage());
+ setTitle("Gitblit Certificate Authority v" + Constants.getVersion() + " (" + Constants.getBuildDate() + ")");
+ setContentPane(getUI());
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent event) {
+ saveSizeAndPosition();
+ }
+
+ @Override
+ public void windowOpened(WindowEvent event) {
+ }
+ });
+
+ File folder = new File(baseFolder).getAbsoluteFile();
+ load(folder);
+
+ setSizeAndPosition();
+ }
+
+ private void setSizeAndPosition() {
+ String sz = null;
+ String pos = null;
+ try {
+ StoredConfig config = getConfig();
+ sz = config.getString("ui", null, "size");
+ pos = config.getString("ui", null, "position");
+ defaultDuration = config.getInt("new", "duration", 365);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+
+ // try to restore saved window size
+ if (StringUtils.isEmpty(sz)) {
+ setSize(900, 600);
+ } else {
+ String[] chunks = sz.split("x");
+ int width = Integer.parseInt(chunks[0]);
+ int height = Integer.parseInt(chunks[1]);
+ setSize(width, height);
+ }
+
+ // try to restore saved window position
+ if (StringUtils.isEmpty(pos)) {
+ setLocationRelativeTo(null);
+ } else {
+ String[] chunks = pos.split(",");
+ int x = Integer.parseInt(chunks[0]);
+ int y = Integer.parseInt(chunks[1]);
+ setLocation(x, y);
+ }
+ }
+
+ private void saveSizeAndPosition() {
+ try {
+ // save window size and position
+ StoredConfig config = getConfig();
+ Dimension sz = GitblitAuthority.this.getSize();
+ config.setString("ui", null, "size",
+ MessageFormat.format("{0,number,0}x{1,number,0}", sz.width, sz.height));
+ Point pos = GitblitAuthority.this.getLocationOnScreen();
+ config.setString("ui", null, "position",
+ MessageFormat.format("{0,number,0},{1,number,0}", pos.x, pos.y));
+ config.save();
+ } catch (Throwable t) {
+ Utils.showException(GitblitAuthority.this, t);
+ }
+ }
+
+ private StoredConfig getConfig() throws IOException, ConfigInvalidException {
+ File configFile = new File(folder, X509Utils.CA_CONFIG);
+ FileBasedConfig config = new FileBasedConfig(configFile, FS.detect());
+ config.load();
+ return config;
+ }
+
+ private IUserService loadUsers(File folder) {
+ File file = new File(folder, "gitblit.properties");
+ if (!file.exists()) {
+ return null;
+ }
+ gitblitSettings = new FileSettings(file.getAbsolutePath());
+ mail = new MailExecutor(gitblitSettings);
+ String us = gitblitSettings.getString(Keys.realm.userService, "${baseFolder}/users.conf");
+ String ext = us.substring(us.lastIndexOf(".") + 1).toLowerCase();
+ IUserService service = null;
+ if (!ext.equals("conf") && !ext.equals("properties")) {
+ if (us.equals("com.gitblit.LdapUserService")) {
+ us = gitblitSettings.getString(Keys.realm.ldap.backingUserService, "${baseFolder}/users.conf");
+ } else if (us.equals("com.gitblit.LdapUserService")) {
+ us = gitblitSettings.getString(Keys.realm.redmine.backingUserService, "${baseFolder}/users.conf");
+ }
+ }
+
+ if (us.endsWith(".conf")) {
+ service = new ConfigUserService(FileUtils.resolveParameter(Constants.baseFolder$, folder, us));
+ } else {
+ throw new RuntimeException("Unsupported user service: " + us);
+ }
+
+ service = new ConfigUserService(FileUtils.resolveParameter(Constants.baseFolder$, folder, us));
+ return service;
+ }
+
+ private void load(File folder) {
+ this.folder = folder;
+ this.userService = loadUsers(folder);
+ System.out.println(Constants.baseFolder$ + " set to " + folder);
+ if (userService == null) {
+ JOptionPane.showMessageDialog(this, MessageFormat.format("Sorry, {0} doesn't look like a Gitblit GO installation.", folder));
+ } else {
+ // build empty certificate model for all users
+ Map<String, UserCertificateModel> map = new HashMap<String, UserCertificateModel>();
+ for (String user : userService.getAllUsernames()) {
+ UserModel model = userService.getUserModel(user);
+ UserCertificateModel ucm = new UserCertificateModel(model);
+ map.put(user, ucm);
+ }
+ File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
+ FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
+ if (certificatesConfigFile.exists()) {
+ try {
+ config.load();
+ // replace user certificate model with actual data
+ List<UserCertificateModel> list = UserCertificateConfig.KEY.parse(config).list;
+ for (UserCertificateModel ucm : list) {
+ ucm.user = userService.getUserModel(ucm.user.username);
+ map.put(ucm.user.username, ucm);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (ConfigInvalidException e) {
+ e.printStackTrace();
+ }
+ }
+
+ tableModel.list = new ArrayList<UserCertificateModel>(map.values());
+ Collections.sort(tableModel.list);
+ tableModel.fireTableDataChanged();
+ Utils.packColumns(table, Utils.MARGIN);
+
+ File caKeystore = new File(folder, X509Utils.CA_KEY_STORE);
+ if (!caKeystore.exists()) {
+
+ if (!X509Utils.unlimitedStrength) {
+ // prompt to confirm user understands JCE Standard Strength encryption
+ int res = JOptionPane.showConfirmDialog(GitblitAuthority.this, Translation.get("gb.jceWarning"),
+ Translation.get("gb.warning"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
+ if (res != JOptionPane.YES_OPTION) {
+ if (Desktop.isDesktopSupported()) {
+ if (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
+ try {
+ Desktop.getDesktop().browse(URI.create("http://www.oracle.com/technetwork/java/javase/downloads/index.html"));
+ } catch (IOException e) {
+ }
+ }
+ }
+ System.exit(1);
+ }
+ }
+
+ // show certificate defaults dialog
+ certificateDefaultsButton.doClick();
+
+ // create "localhost" ssl certificate
+ prepareX509Infrastructure();
+ }
+ }
+ }
+
+ private boolean prepareX509Infrastructure() {
+ if (caKeystorePassword == null) {
+ JPasswordField pass = new JPasswordField(10);
+ pass.setText(caKeystorePassword);
+ pass.addAncestorListener(new RequestFocusListener());
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.add(new JLabel(Translation.get("gb.enterKeystorePassword")), BorderLayout.NORTH);
+ panel.add(pass, BorderLayout.CENTER);
+ int result = JOptionPane.showConfirmDialog(GitblitAuthority.this, panel, Translation.get("gb.password"), JOptionPane.OK_CANCEL_OPTION);
+ if (result == JOptionPane.OK_OPTION) {
+ caKeystorePassword = new String(pass.getPassword());
+ } else {
+ return false;
+ }
+ }
+
+ X509Metadata metadata = new X509Metadata("localhost", caKeystorePassword);
+ setMetadataDefaults(metadata);
+ metadata.notAfter = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR);
+ X509Utils.prepareX509Infrastructure(metadata, folder, this);
+ return true;
+ }
+
+ private List<X509Certificate> findCerts(File folder, String username) {
+ List<X509Certificate> list = new ArrayList<X509Certificate>();
+ File userFolder = new File(folder, X509Utils.CERTS + File.separator + username);
+ if (!userFolder.exists()) {
+ return list;
+ }
+ File [] certs = userFolder.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.toLowerCase().endsWith(".cer") || name.toLowerCase().endsWith(".crt");
+ }
+ });
+ try {
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ for (File cert : certs) {
+ BufferedInputStream is = new BufferedInputStream(new FileInputStream(cert));
+ X509Certificate x509 = (X509Certificate) factory.generateCertificate(is);
+ is.close();
+ list.add(x509);
+ }
+ } catch (Exception e) {
+ Utils.showException(GitblitAuthority.this, e);
+ }
+ return list;
+ }
+
+ private Container getUI() {
+ userCertificatePanel = new UserCertificatePanel(this) {
+
+ private static final long serialVersionUID = 1L;
+ @Override
+ public Insets getInsets() {
+ return Utils.INSETS;
+ }
+
+ @Override
+ public boolean isAllowEmail() {
+ return mail.isReady();
+ }
+
+ @Override
+ public Date getDefaultExpiration() {
+ Calendar c = Calendar.getInstance();
+ c.add(Calendar.DATE, defaultDuration);
+ c.set(Calendar.HOUR_OF_DAY, 0);
+ c.set(Calendar.MINUTE, 0);
+ c.set(Calendar.SECOND, 0);
+ c.set(Calendar.MILLISECOND, 0);
+ return c.getTime();
+ }
+
+ @Override
+ public boolean saveUser(String username, UserCertificateModel ucm) {
+ return userService.updateUserModel(username, ucm.user);
+ }
+
+ @Override
+ public boolean newCertificate(UserCertificateModel ucm, X509Metadata metadata, boolean sendEmail) {
+ if (!prepareX509Infrastructure()) {
+ return false;
+ }
+
+ Date notAfter = metadata.notAfter;
+ setMetadataDefaults(metadata);
+ metadata.notAfter = notAfter;
+
+ // set user's specified OID values
+ UserModel user = ucm.user;
+ if (!StringUtils.isEmpty(user.organizationalUnit)) {
+ metadata.oids.put("OU", user.organizationalUnit);
+ }
+ if (!StringUtils.isEmpty(user.organization)) {
+ metadata.oids.put("O", user.organization);
+ }
+ if (!StringUtils.isEmpty(user.locality)) {
+ metadata.oids.put("L", user.locality);
+ }
+ if (!StringUtils.isEmpty(user.stateProvince)) {
+ metadata.oids.put("ST", user.stateProvince);
+ }
+ if (!StringUtils.isEmpty(user.countryCode)) {
+ metadata.oids.put("C", user.countryCode);
+ }
+
+ File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE);
+ File zip = X509Utils.newClientBundle(metadata, caKeystoreFile, caKeystorePassword, GitblitAuthority.this);
+
+ // save latest expiration date
+ if (ucm.expires == null || metadata.notAfter.before(ucm.expires)) {
+ ucm.expires = metadata.notAfter;
+ }
+
+ updateAuthorityConfig(ucm);
+
+ // refresh user
+ ucm.certs = null;
+ int modelIndex = table.convertRowIndexToModel(table.getSelectedRow());
+ tableModel.fireTableDataChanged();
+ table.getSelectionModel().setSelectionInterval(modelIndex, modelIndex);
+
+ if (sendEmail) {
+ sendEmail(user, metadata, zip);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean revoke(UserCertificateModel ucm, X509Certificate cert, RevocationReason reason) {
+ if (!prepareX509Infrastructure()) {
+ return false;
+ }
+
+ File caRevocationList = new File(folder, X509Utils.CA_REVOCATION_LIST);
+ File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE);
+ if (X509Utils.revoke(cert, reason, caRevocationList, caKeystoreFile, caKeystorePassword, GitblitAuthority.this)) {
+ File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
+ FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
+ if (certificatesConfigFile.exists()) {
+ try {
+ config.load();
+ } catch (Exception e) {
+ Utils.showException(GitblitAuthority.this, e);
+ }
+ }
+ // add serial to revoked list
+ ucm.revoke(cert.getSerialNumber(), reason);
+ ucm.update(config);
+ try {
+ config.save();
+ } catch (Exception e) {
+ Utils.showException(GitblitAuthority.this, e);
+ }
+
+ // refresh user
+ ucm.certs = null;
+ int modelIndex = table.convertRowIndexToModel(table.getSelectedRow());
+ tableModel.fireTableDataChanged();
+ table.getSelectionModel().setSelectionInterval(modelIndex, modelIndex);
+
+ return true;
+ }
+
+ return false;
+ }
+ };
+
+ table = Utils.newTable(tableModel, Utils.DATE_FORMAT);
+ table.setRowSorter(defaultSorter);
+ table.setDefaultRenderer(CertificateStatus.class, new CertificateStatusRenderer());
+ table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getValueIsAdjusting()) {
+ return;
+ }
+ int row = table.getSelectedRow();
+ if (row < 0) {
+ return;
+ }
+ int modelIndex = table.convertRowIndexToModel(row);
+ UserCertificateModel ucm = tableModel.get(modelIndex);
+ if (ucm.certs == null) {
+ ucm.certs = findCerts(folder, ucm.user.username);
+ }
+ userCertificatePanel.setUserCertificateModel(ucm);
+ }
+ });
+
+ JPanel usersPanel = new JPanel(new BorderLayout()) {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Insets getInsets() {
+ return Utils.INSETS;
+ }
+ };
+ usersPanel.add(new HeaderPanel(Translation.get("gb.users"), "users_16x16.png"), BorderLayout.NORTH);
+ usersPanel.add(new JScrollPane(table), BorderLayout.CENTER);
+ usersPanel.setMinimumSize(new Dimension(400, 10));
+
+ certificateDefaultsButton = new JButton(new ImageIcon(getClass().getResource("/settings_16x16.png")));
+ certificateDefaultsButton.setFocusable(false);
+ certificateDefaultsButton.setToolTipText(Translation.get("gb.newCertificateDefaults"));
+ certificateDefaultsButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ X509Metadata metadata = new X509Metadata("whocares", "whocares");
+ File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
+ FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
+ NewCertificateConfig certificateConfig = null;
+ if (certificatesConfigFile.exists()) {
+ try {
+ config.load();
+ } catch (Exception x) {
+ Utils.showException(GitblitAuthority.this, x);
+ }
+ certificateConfig = NewCertificateConfig.KEY.parse(config);
+ certificateConfig.update(metadata);
+ }
+ InputVerifier verifier = new InputVerifier() {
+ public boolean verify(JComponent comp) {
+ boolean returnValue;
+ JTextField textField = (JTextField) comp;
+ try {
+ Integer.parseInt(textField.getText());
+ returnValue = true;
+ } catch (NumberFormatException e) {
+ returnValue = false;
+ }
+ return returnValue;
+ }
+ };
+
+ JTextField siteNameTF = new JTextField(20);
+ siteNameTF.setText(gitblitSettings.getString(Keys.web.siteName, "Gitblit"));
+ JPanel siteNamePanel = Utils.newFieldPanel(Translation.get("gb.siteName"),
+ siteNameTF, Translation.get("gb.siteNameDescription"));
+
+ JTextField validityTF = new JTextField(4);
+ validityTF.setInputVerifier(verifier);
+ validityTF.setVerifyInputWhenFocusTarget(true);
+ validityTF.setText("" + certificateConfig.duration);
+ JPanel validityPanel = Utils.newFieldPanel(Translation.get("gb.validity"),
+ validityTF, Translation.get("gb.duration.days").replace("{0}", "").trim());
+
+ JPanel p1 = new JPanel(new GridLayout(0, 1, 5, 2));
+ p1.add(siteNamePanel);
+ p1.add(validityPanel);
+
+ DefaultOidsPanel oids = new DefaultOidsPanel(metadata);
+
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.add(p1, BorderLayout.NORTH);
+ panel.add(oids, BorderLayout.CENTER);
+
+ int result = JOptionPane.showConfirmDialog(GitblitAuthority.this,
+ panel, Translation.get("gb.newCertificateDefaults"), JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE, new ImageIcon(getClass().getResource("/settings_32x32.png")));
+ if (result == JOptionPane.OK_OPTION) {
+ try {
+ oids.update(metadata);
+ certificateConfig.duration = Integer.parseInt(validityTF.getText());
+ certificateConfig.store(config, metadata);
+ config.save();
+
+ Map<String, String> updates = new HashMap<String, String>();
+ updates.put(Keys.web.siteName, siteNameTF.getText());
+ gitblitSettings.saveSettings(updates);
+ } catch (Exception e1) {
+ Utils.showException(GitblitAuthority.this, e1);
+ }
+ }
+ }
+ });
+
+ newSSLCertificate = new JButton(new ImageIcon(getClass().getResource("/rosette_16x16.png")));
+ newSSLCertificate.setFocusable(false);
+ newSSLCertificate.setToolTipText(Translation.get("gb.newSSLCertificate"));
+ newSSLCertificate.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Date defaultExpiration = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR);
+ NewSSLCertificateDialog dialog = new NewSSLCertificateDialog(GitblitAuthority.this, defaultExpiration);
+ dialog.setModal(true);
+ dialog.setVisible(true);
+ if (dialog.isCanceled()) {
+ return;
+ }
+ final Date expires = dialog.getExpiration();
+ final String hostname = dialog.getHostname();
+ final boolean serveCertificate = dialog.isServeCertificate();
+
+ AuthorityWorker worker = new AuthorityWorker(GitblitAuthority.this) {
+
+ @Override
+ protected Boolean doRequest() throws IOException {
+ if (!prepareX509Infrastructure()) {
+ return false;
+ }
+
+ // read CA private key and certificate
+ File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE);
+ PrivateKey caPrivateKey = X509Utils.getPrivateKey(X509Utils.CA_ALIAS, caKeystoreFile, caKeystorePassword);
+ X509Certificate caCert = X509Utils.getCertificate(X509Utils.CA_ALIAS, caKeystoreFile, caKeystorePassword);
+
+ // generate new SSL certificate
+ X509Metadata metadata = new X509Metadata(hostname, caKeystorePassword);
+ setMetadataDefaults(metadata);
+ metadata.notAfter = expires;
+ File serverKeystoreFile = new File(folder, X509Utils.SERVER_KEY_STORE);
+ X509Certificate cert = X509Utils.newSSLCertificate(metadata, caPrivateKey, caCert, serverKeystoreFile, GitblitAuthority.this);
+ boolean hasCert = cert != null;
+ if (hasCert && serveCertificate) {
+ // update Gitblit https connector alias
+ Map<String, String> updates = new HashMap<String, String>();
+ updates.put(Keys.server.certificateAlias, metadata.commonName);
+ gitblitSettings.saveSettings(updates);
+ }
+ return hasCert;
+ }
+
+ @Override
+ protected void onSuccess() {
+ if (serveCertificate) {
+ JOptionPane.showMessageDialog(GitblitAuthority.this,
+ MessageFormat.format(Translation.get("gb.sslCertificateGeneratedRestart"), hostname),
+ Translation.get("gb.newSSLCertificate"), JOptionPane.INFORMATION_MESSAGE);
+ } else {
+ JOptionPane.showMessageDialog(GitblitAuthority.this,
+ MessageFormat.format(Translation.get("gb.sslCertificateGenerated"), hostname),
+ Translation.get("gb.newSSLCertificate"), JOptionPane.INFORMATION_MESSAGE);
+ }
+ }
+ };
+
+ worker.execute();
+ }
+ });
+
+ JButton emailBundle = new JButton(new ImageIcon(getClass().getResource("/mail_16x16.png")));
+ emailBundle.setFocusable(false);
+ emailBundle.setToolTipText(Translation.get("gb.emailCertificateBundle"));
+ emailBundle.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int row = table.getSelectedRow();
+ if (row < 0) {
+ return;
+ }
+ int modelIndex = table.convertRowIndexToModel(row);
+ final UserCertificateModel ucm = tableModel.get(modelIndex);
+ if (ArrayUtils.isEmpty(ucm.certs)) {
+ JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.pleaseGenerateClientCertificate"), ucm.user.getDisplayName()));
+ }
+ final File zip = new File(folder, X509Utils.CERTS + File.separator + ucm.user.username + File.separator + ucm.user.username + ".zip");
+ if (!zip.exists()) {
+ return;
+ }
+
+ AuthorityWorker worker = new AuthorityWorker(GitblitAuthority.this) {
+ @Override
+ protected Boolean doRequest() throws IOException {
+ X509Metadata metadata = new X509Metadata(ucm.user.username, "whocares");
+ metadata.serverHostname = gitblitSettings.getString(Keys.web.siteName, Constants.NAME);
+ if (StringUtils.isEmpty(metadata.serverHostname)) {
+ metadata.serverHostname = Constants.NAME;
+ }
+ metadata.userDisplayname = ucm.user.getDisplayName();
+ return sendEmail(ucm.user, metadata, zip);
+ }
+
+ @Override
+ protected void onSuccess() {
+ JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.clientCertificateBundleSent"),
+ ucm.user.getDisplayName()));
+ }
+
+ };
+ worker.execute();
+ }
+ });
+
+ JButton logButton = new JButton(new ImageIcon(getClass().getResource("/script_16x16.png")));
+ logButton.setFocusable(false);
+ logButton.setToolTipText(Translation.get("gb.log"));
+ logButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ File log = new File(folder, X509Utils.CERTS + File.separator + "log.txt");
+ if (log.exists()) {
+ String content = FileUtils.readContent(log, "\n");
+ JTextArea textarea = new JTextArea(content);
+ JScrollPane scrollPane = new JScrollPane(textarea);
+ scrollPane.setPreferredSize(new Dimension(700, 400));
+ JOptionPane.showMessageDialog(GitblitAuthority.this, scrollPane, log.getAbsolutePath(), JOptionPane.INFORMATION_MESSAGE);
+ }
+ }
+ });
+
+ final JTextField filterTextfield = new JTextField(15);
+ filterTextfield.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ filterUsers(filterTextfield.getText());
+ }
+ });
+ filterTextfield.addKeyListener(new KeyAdapter() {
+ public void keyReleased(KeyEvent e) {
+ filterUsers(filterTextfield.getText());
+ }
+ });
+
+ JToolBar buttonControls = new JToolBar(JToolBar.HORIZONTAL);
+ buttonControls.setFloatable(false);
+ buttonControls.add(certificateDefaultsButton);
+ buttonControls.add(newSSLCertificate);
+ buttonControls.add(emailBundle);
+ buttonControls.add(logButton);
+
+ JPanel userControls = new JPanel(new FlowLayout(FlowLayout.RIGHT, Utils.MARGIN, Utils.MARGIN));
+ userControls.add(new JLabel(Translation.get("gb.filter")));
+ userControls.add(filterTextfield);
+
+ JPanel topPanel = new JPanel(new BorderLayout(0, 0));
+ topPanel.add(buttonControls, BorderLayout.WEST);
+ topPanel.add(userControls, BorderLayout.EAST);
+
+ JPanel leftPanel = new JPanel(new BorderLayout());
+ leftPanel.add(topPanel, BorderLayout.NORTH);
+ leftPanel.add(usersPanel, BorderLayout.CENTER);
+
+ userCertificatePanel.setMinimumSize(new Dimension(375, 10));
+
+ JLabel statusLabel = new JLabel();
+ statusLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+ if (X509Utils.unlimitedStrength) {
+ statusLabel.setText("JCE Unlimited Strength Jurisdiction Policy");
+ } else {
+ statusLabel.setText("JCE Standard Encryption Policy");
+ }
+
+ JPanel root = new JPanel(new BorderLayout()) {
+ private static final long serialVersionUID = 1L;
+ public Insets getInsets() {
+ return Utils.INSETS;
+ }
+ };
+ JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, userCertificatePanel);
+ splitPane.setDividerLocation(1d);
+ root.add(splitPane, BorderLayout.CENTER);
+ root.add(statusLabel, BorderLayout.SOUTH);
+ return root;
+ }
+
+ private void filterUsers(final String fragment) {
+ if (StringUtils.isEmpty(fragment)) {
+ table.setRowSorter(defaultSorter);
+ return;
+ }
+ RowFilter<UserCertificateTableModel, Object> containsFilter = new RowFilter<UserCertificateTableModel, Object>() {
+ public boolean include(Entry<? extends UserCertificateTableModel, ? extends Object> entry) {
+ for (int i = entry.getValueCount() - 1; i >= 0; i--) {
+ if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ TableRowSorter<UserCertificateTableModel> sorter = new TableRowSorter<UserCertificateTableModel>(
+ tableModel);
+ sorter.setRowFilter(containsFilter);
+ table.setRowSorter(sorter);
+ }
+
+ @Override
+ public void log(String message) {
+ BufferedWriter writer = null;
+ try {
+ writer = new BufferedWriter(new FileWriter(new File(folder, X509Utils.CERTS + File.separator + "log.txt"), true));
+ writer.write(MessageFormat.format("{0,date,yyyy-MM-dd HH:mm}: {1}", new Date(), message));
+ writer.newLine();
+ writer.flush();
+ } catch (Exception e) {
+ LoggerFactory.getLogger(GitblitAuthority.class).error("Failed to append log entry!", e);
+ } finally {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ private boolean sendEmail(UserModel user, X509Metadata metadata, File zip) {
+ // send email
+ try {
+ if (mail.isReady()) {
+ Message message = mail.createMessage(user.emailAddress);
+ message.setSubject("Your Gitblit client certificate for " + metadata.serverHostname);
+
+ // body of email
+ String body = X509Utils.processTemplate(new File(folder, X509Utils.CERTS + File.separator + "mail.tmpl"), metadata);
+ if (StringUtils.isEmpty(body)) {
+ body = MessageFormat.format("Hi {0}\n\nHere is your client certificate bundle.\nInside the zip file are installation instructions.", user.getDisplayName());
+ }
+ Multipart mp = new MimeMultipart();
+ MimeBodyPart messagePart = new MimeBodyPart();
+ messagePart.setText(body);
+ mp.addBodyPart(messagePart);
+
+ // attach zip
+ MimeBodyPart filePart = new MimeBodyPart();
+ FileDataSource fds = new FileDataSource(zip);
+ filePart.setDataHandler(new DataHandler(fds));
+ filePart.setFileName(fds.getName());
+ mp.addBodyPart(filePart);
+
+ message.setContent(mp);
+
+ mail.sendNow(message);
+ return true;
+ } else {
+ JOptionPane.showMessageDialog(GitblitAuthority.this, "Sorry, the mail server settings are not configured properly.\nCan not send email.", Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
+ }
+ } catch (Exception e) {
+ Utils.showException(GitblitAuthority.this, e);
+ }
+ return false;
+ }
+
+ private void setMetadataDefaults(X509Metadata metadata) {
+ metadata.serverHostname = gitblitSettings.getString(Keys.web.siteName, Constants.NAME);
+ if (StringUtils.isEmpty(metadata.serverHostname)) {
+ metadata.serverHostname = Constants.NAME;
+ }
+
+ // set default values from config file
+ File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
+ FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
+ if (certificatesConfigFile.exists()) {
+ try {
+ config.load();
+ } catch (Exception e) {
+ Utils.showException(GitblitAuthority.this, e);
+ }
+ NewCertificateConfig certificateConfig = NewCertificateConfig.KEY.parse(config);
+ certificateConfig.update(metadata);
+ }
+ }
+
+ private void updateAuthorityConfig(UserCertificateModel ucm) {
+ File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
+ FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
+ if (certificatesConfigFile.exists()) {
+ try {
+ config.load();
+ } catch (Exception e) {
+ Utils.showException(GitblitAuthority.this, e);
+ }
+ }
+ ucm.update(config);
+ try {
+ config.save();
+ } catch (Exception e) {
+ Utils.showException(GitblitAuthority.this, e);
+ }
+ }
+}
diff --git a/src/main/java/com/gitblit/authority/Launcher.java b/src/main/java/com/gitblit/authority/Launcher.java
new file mode 100644
index 00000000..1da97149
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/Launcher.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2012 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.authority;
+
+import java.awt.Color;
+import java.awt.EventQueue;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.SplashScreen;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import com.gitblit.Constants;
+import com.gitblit.client.Translation;
+
+/**
+ * Downloads dependencies and launches Gitblit Authority.
+ *
+ * @author James Moger
+ *
+ */
+public class Launcher {
+
+ public static final boolean DEBUG = false;
+
+ /**
+ * Parameters of the method to add an URL to the System classes.
+ */
+ private static final Class<?>[] PARAMETERS = new Class[] { URL.class };
+
+
+ public static void main(String[] args) {
+ final SplashScreen splash = SplashScreen.getSplashScreen();
+
+ File libFolder = new File("ext");
+ List<File> jars = findJars(libFolder.getAbsoluteFile());
+
+ // sort the jars by name and then reverse the order so the newer version
+ // of the library gets loaded in the event that this is an upgrade
+ Collections.sort(jars);
+ Collections.reverse(jars);
+ for (File jar : jars) {
+ try {
+ updateSplash(splash, Translation.get("gb.loading") + " " + jar.getName() + "...");
+ addJarFile(jar);
+ } catch (IOException e) {
+
+ }
+ }
+
+ updateSplash(splash, Translation.get("gb.starting") + " Gitblit Authority...");
+ GitblitAuthority.main(args);
+ }
+
+ private static void updateSplash(final SplashScreen splash, final String string) {
+ if (splash == null) {
+ return;
+ }
+ try {
+ EventQueue.invokeAndWait(new Runnable() {
+ public void run() {
+ Graphics2D g = splash.createGraphics();
+ if (g != null) {
+ // Splash is 320x120
+ FontMetrics fm = g.getFontMetrics();
+
+ // paint startup status
+ g.setColor(Color.darkGray);
+ int h = fm.getHeight() + fm.getMaxDescent();
+ int x = 5;
+ int y = 115;
+ int w = 320 - 2 * x;
+ g.fillRect(x, y - h, w, h);
+ g.setColor(Color.lightGray);
+ g.drawRect(x, y - h, w, h);
+ g.setColor(Color.WHITE);
+ int xw = fm.stringWidth(string);
+ g.drawString(string, x + ((w - xw) / 2), y - 5);
+
+ // paint version
+ String ver = "v" + Constants.getVersion();
+ int vw = g.getFontMetrics().stringWidth(ver);
+ g.drawString(ver, 320 - vw - 5, 34);
+ g.dispose();
+ splash.update();
+ }
+ }
+ });
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+
+ public static List<File> findJars(File folder) {
+ List<File> jars = new ArrayList<File>();
+ if (folder.exists()) {
+ File[] libs = folder.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return !file.isDirectory() && file.getName().toLowerCase().endsWith(".jar");
+ }
+ });
+ if (libs != null && libs.length > 0) {
+ jars.addAll(Arrays.asList(libs));
+ if (DEBUG) {
+ for (File jar : jars) {
+ System.out.println("found " + jar);
+ }
+ }
+ }
+ }
+
+ return jars;
+ }
+
+ /**
+ * Adds a file to the classpath
+ *
+ * @param f
+ * the file to be added
+ * @throws IOException
+ */
+ public static void addJarFile(File f) throws IOException {
+ if (f.getName().indexOf("-sources") > -1 || f.getName().indexOf("-javadoc") > -1) {
+ // don't add source or javadoc jars to runtime classpath
+ return;
+ }
+ URL u = f.toURI().toURL();
+ if (DEBUG) {
+ System.out.println("load=" + u.toExternalForm());
+ }
+ URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
+ Class<?> sysclass = URLClassLoader.class;
+ try {
+ Method method = sysclass.getDeclaredMethod("addURL", PARAMETERS);
+ method.setAccessible(true);
+ method.invoke(sysloader, new Object[] { u });
+ } catch (Throwable t) {
+ throw new IOException(MessageFormat.format(
+ "Error, could not add {0} to system classloader", f.getPath()), t);
+ }
+ }
+}
diff --git a/src/main/java/com/gitblit/authority/NewCertificateConfig.java b/src/main/java/com/gitblit/authority/NewCertificateConfig.java
new file mode 100644
index 00000000..ca047c82
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/NewCertificateConfig.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2012 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.authority;
+
+import java.util.Date;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Config.SectionParser;
+
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.utils.X509Utils.X509Metadata;
+
+/**
+ * Certificate config file parser.
+ *
+ * @author James Moger
+ */
+public class NewCertificateConfig {
+ public static final SectionParser<NewCertificateConfig> KEY = new SectionParser<NewCertificateConfig>() {
+ public NewCertificateConfig parse(final Config cfg) {
+ return new NewCertificateConfig(cfg);
+ }
+ };
+
+ public String OU;
+ public String O;
+ public String L;
+ public String ST;
+ public String C;
+
+ public int duration;
+
+ private NewCertificateConfig(final Config c) {
+ duration = c.getInt("new", null, "duration", 0);
+ OU = c.getString("new", null, "organizationalUnit");
+ O = c.getString("new", null, "organization");
+ L = c.getString("new", null, "locality");
+ ST = c.getString("new", null, "stateProvince");
+ C = c.getString("new", null, "countryCode");
+ }
+
+ public void update(X509Metadata metadata) {
+ update(metadata, "OU", OU);
+ update(metadata, "O", O);
+ update(metadata, "L", L);
+ update(metadata, "ST", ST);
+ update(metadata, "C", C);
+ if (duration > 0) {
+ metadata.notAfter = new Date(System.currentTimeMillis() + duration*TimeUtils.ONEDAY);
+ }
+ }
+
+ private void update(X509Metadata metadata, String oid, String value) {
+ if (!StringUtils.isEmpty(value)) {
+ metadata.oids.put(oid, value);
+ }
+ }
+
+ public void store(Config c, X509Metadata metadata) {
+ store(c, "new", "organizationalUnit", metadata.getOID("OU", null));
+ store(c, "new", "organization", metadata.getOID("O", null));
+ store(c, "new", "locality", metadata.getOID("L", null));
+ store(c, "new", "stateProvince", metadata.getOID("ST", null));
+ store(c, "new", "countryCode", metadata.getOID("C", null));
+ if (duration <= 0) {
+ c.unset("new", null, "duration");
+ } else {
+ c.setInt("new", null, "duration", duration);
+ }
+ }
+
+ private void store(Config c, String section, String name, String value) {
+ if (StringUtils.isEmpty(value)) {
+ c.unset(section, null, name);
+ } else {
+ c.setString(section, null, name, value);
+ }
+ }
+ } \ No newline at end of file
diff --git a/src/main/java/com/gitblit/authority/NewClientCertificateDialog.java b/src/main/java/com/gitblit/authority/NewClientCertificateDialog.java
new file mode 100644
index 00000000..3d214390
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/NewClientCertificateDialog.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2012 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.authority;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Date;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+
+import org.bouncycastle.util.Arrays;
+
+import com.gitblit.client.HeaderPanel;
+import com.gitblit.client.Translation;
+import com.gitblit.utils.StringUtils;
+import com.toedter.calendar.JDateChooser;
+
+public class NewClientCertificateDialog extends JDialog {
+
+ private static final long serialVersionUID = 1L;
+
+ JDateChooser expirationDate;
+ JPasswordField pw1;
+ JPasswordField pw2;
+ JTextField hint;
+ JCheckBox sendEmail;
+ boolean isCanceled = true;
+
+ public NewClientCertificateDialog(Frame owner, String displayname, Date defaultExpiration, boolean allowEmail) {
+ super(owner);
+
+ setTitle(Translation.get("gb.newCertificate"));
+
+ JPanel content = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN)) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Insets getInsets() {
+
+ return Utils.INSETS;
+ }
+ };
+
+ expirationDate = new JDateChooser(defaultExpiration);
+ pw1 = new JPasswordField(20);
+ pw2 = new JPasswordField(20);
+ hint = new JTextField(20);
+ sendEmail = new JCheckBox(Translation.get("gb.sendEmail"));
+
+ JPanel panel = new JPanel(new GridLayout(0, 2, Utils.MARGIN, Utils.MARGIN));
+
+ panel.add(new JLabel(Translation.get("gb.expires")));
+ panel.add(expirationDate);
+
+ panel.add(new JLabel(Translation.get("gb.password")));
+ panel.add(pw1);
+
+ panel.add(new JLabel(Translation.get("gb.confirmPassword")));
+ panel.add(pw2);
+
+ panel.add(new JLabel(Translation.get("gb.passwordHint")));
+ panel.add(hint);
+
+ if (allowEmail) {
+ panel.add(new JLabel(""));
+ panel.add(sendEmail);
+ }
+
+
+ JButton ok = new JButton(Translation.get("gb.ok"));
+ ok.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ if (validateInputs()) {
+ isCanceled = false;
+ setVisible(false);
+ }
+ }
+ });
+ JButton cancel = new JButton(Translation.get("gb.cancel"));
+ cancel.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ isCanceled = true;
+ setVisible(false);
+ }
+ });
+
+ JPanel controls = new JPanel();
+ controls.add(ok);
+ controls.add(cancel);
+
+ JTextArea message = new JTextArea(Translation.get("gb.newClientCertificateMessage"));
+ message.setLineWrap(true);
+ message.setWrapStyleWord(true);
+ message.setEditable(false);
+ message.setRows(6);
+ message.setPreferredSize(new Dimension(300, 100));
+
+ content.add(new JScrollPane(message), BorderLayout.CENTER);
+ content.add(panel, BorderLayout.NORTH);
+ content.add(controls, BorderLayout.SOUTH);
+
+ getContentPane().add(new HeaderPanel(Translation.get("gb.newCertificate") + " (" + displayname + ")", "rosette_16x16.png"), BorderLayout.NORTH);
+ getContentPane().add(content, BorderLayout.CENTER);
+ pack();
+
+ setLocationRelativeTo(owner);
+ }
+
+ private boolean validateInputs() {
+ if (getExpiration().getTime() < System.currentTimeMillis()) {
+ // expires before now
+ JOptionPane.showMessageDialog(this, Translation.get("gb.invalidExpirationDate"),
+ Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ if (pw1.getPassword().length == 0 || !Arrays.areEqual(pw1.getPassword(), pw2.getPassword())) {
+ // password mismatch
+ JOptionPane.showMessageDialog(this, Translation.get("gb.passwordsDoNotMatch"),
+ Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ if (StringUtils.isEmpty(getPasswordHint())) {
+ // must have hint
+ JOptionPane.showMessageDialog(this, Translation.get("gb.passwordHintRequired"),
+ Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ return true;
+ }
+
+ public String getPassword() {
+ return new String(pw1.getPassword());
+ }
+
+ public String getPasswordHint() {
+ return hint.getText();
+ }
+
+ public Date getExpiration() {
+ return expirationDate.getDate();
+ }
+
+ public boolean sendEmail() {
+ return sendEmail.isSelected();
+ }
+
+ public boolean isCanceled() {
+ return isCanceled;
+ }
+}
diff --git a/src/main/java/com/gitblit/authority/NewSSLCertificateDialog.java b/src/main/java/com/gitblit/authority/NewSSLCertificateDialog.java
new file mode 100644
index 00000000..821e9e9f
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/NewSSLCertificateDialog.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2012 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.authority;
+
+import java.awt.BorderLayout;
+import java.awt.Frame;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Date;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import com.gitblit.client.HeaderPanel;
+import com.gitblit.client.Translation;
+import com.gitblit.utils.StringUtils;
+import com.toedter.calendar.JDateChooser;
+
+public class NewSSLCertificateDialog extends JDialog {
+
+ private static final long serialVersionUID = 1L;
+
+ JDateChooser expirationDate;
+ JTextField hostname;
+ JCheckBox serveCertificate;
+ boolean isCanceled = true;
+
+ public NewSSLCertificateDialog(Frame owner, Date defaultExpiration) {
+ super(owner);
+
+ setTitle(Translation.get("gb.newSSLCertificate"));
+
+ JPanel content = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN)) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Insets getInsets() {
+
+ return Utils.INSETS;
+ }
+ };
+
+ expirationDate = new JDateChooser(defaultExpiration);
+ hostname = new JTextField(20);
+ serveCertificate = new JCheckBox(Translation.get("gb.serveCertificate"), true);
+
+ JPanel panel = new JPanel(new GridLayout(0, 2, Utils.MARGIN, Utils.MARGIN));
+
+ panel.add(new JLabel(Translation.get("gb.hostname")));
+ panel.add(hostname);
+
+ panel.add(new JLabel(Translation.get("gb.expires")));
+ panel.add(expirationDate);
+
+ panel.add(new JLabel(""));
+ panel.add(serveCertificate);
+
+ JButton ok = new JButton(Translation.get("gb.ok"));
+ ok.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ if (validateInputs()) {
+ isCanceled = false;
+ setVisible(false);
+ }
+ }
+ });
+ JButton cancel = new JButton(Translation.get("gb.cancel"));
+ cancel.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ isCanceled = true;
+ setVisible(false);
+ }
+ });
+
+ JPanel controls = new JPanel();
+ controls.add(ok);
+ controls.add(cancel);
+
+ content.add(panel, BorderLayout.CENTER);
+ content.add(controls, BorderLayout.SOUTH);
+
+ getContentPane().add(new HeaderPanel(Translation.get("gb.newSSLCertificate"), "rosette_16x16.png"), BorderLayout.NORTH);
+ getContentPane().add(content, BorderLayout.CENTER);
+ pack();
+
+ setLocationRelativeTo(owner);
+ }
+
+ private boolean validateInputs() {
+ if (getExpiration().getTime() < System.currentTimeMillis()) {
+ // expires before now
+ JOptionPane.showMessageDialog(this, Translation.get("gb.invalidExpirationDate"),
+ Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ if (StringUtils.isEmpty(getHostname())) {
+ // must have hostname
+ JOptionPane.showMessageDialog(this, Translation.get("gb.hostnameRequired"),
+ Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ return true;
+ }
+
+ public String getHostname() {
+ return hostname.getText();
+ }
+
+ public Date getExpiration() {
+ return expirationDate.getDate();
+ }
+
+ public boolean isServeCertificate() {
+ return serveCertificate.isSelected();
+ }
+
+ public boolean isCanceled() {
+ return isCanceled;
+ }
+}
diff --git a/src/main/java/com/gitblit/authority/RequestFocusListener.java b/src/main/java/com/gitblit/authority/RequestFocusListener.java
new file mode 100644
index 00000000..e9368686
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/RequestFocusListener.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2012 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.authority;
+import javax.swing.*;
+import javax.swing.event.*;
+
+/**
+ * Convenience class to request focus on a component.
+ *
+ * When the component is added to a realized Window then component will
+ * request focus immediately, since the ancestorAdded event is fired
+ * immediately.
+ *
+ * When the component is added to a non realized Window, then the focus
+ * request will be made once the window is realized, since the
+ * ancestorAdded event will not be fired until then.
+ *
+ * Using the default constructor will cause the listener to be removed
+ * from the component once the AncestorEvent is generated. A second constructor
+ * allows you to specify a boolean value of false to prevent the
+ * AncestorListener from being removed when the event is generated. This will
+ * allow you to reuse the listener each time the event is generated.
+ *
+ * @author Rob Camick
+ */
+public class RequestFocusListener implements AncestorListener
+{
+ private boolean removeListener;
+
+ /*
+ * Convenience constructor. The listener is only used once and then it is
+ * removed from the component.
+ */
+ public RequestFocusListener()
+ {
+ this(true);
+ }
+
+ /*
+ * Constructor that controls whether this listen can be used once or
+ * multiple times.
+ *
+ * @param removeListener when true this listener is only invoked once
+ * otherwise it can be invoked multiple times.
+ */
+ public RequestFocusListener(boolean removeListener)
+ {
+ this.removeListener = removeListener;
+ }
+
+ @Override
+ public void ancestorAdded(AncestorEvent e)
+ {
+ JComponent component = e.getComponent();
+ component.requestFocusInWindow();
+
+ if (removeListener)
+ component.removeAncestorListener( this );
+ }
+
+ @Override
+ public void ancestorMoved(AncestorEvent e) {}
+
+ @Override
+ public void ancestorRemoved(AncestorEvent e) {}
+}
diff --git a/src/main/java/com/gitblit/authority/UserCertificateConfig.java b/src/main/java/com/gitblit/authority/UserCertificateConfig.java
new file mode 100644
index 00000000..5ec76f77
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/UserCertificateConfig.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 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.authority;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Config.SectionParser;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants;
+import com.gitblit.models.UserModel;
+
+/**
+ * User certificate config section parser.
+ *
+ * @author James Moger
+ */
+public class UserCertificateConfig {
+ public static final SectionParser<UserCertificateConfig> KEY = new SectionParser<UserCertificateConfig>() {
+ public UserCertificateConfig parse(final Config cfg) {
+ return new UserCertificateConfig(cfg);
+ }
+ };
+
+ public final List<UserCertificateModel> list;
+
+ private UserCertificateConfig(final Config c) {
+ SimpleDateFormat df = new SimpleDateFormat(Constants.ISO8601);
+ list = new ArrayList<UserCertificateModel>();
+ for (String username : c.getSubsections("user")) {
+ UserCertificateModel uc = new UserCertificateModel(new UserModel(username));
+ try {
+ uc.expires = df.parse(c.getString("user", username, "expires"));
+ } catch (ParseException e) {
+ LoggerFactory.getLogger(UserCertificateConfig.class).error("Failed to parse date!", e);
+ } catch (NullPointerException e) {
+ }
+ uc.notes = c.getString("user", username, "notes");
+ uc.revoked = new ArrayList<String>(Arrays.asList(c.getStringList("user", username, "revoked")));
+ list.add(uc);
+ }
+ }
+
+ public UserCertificateModel getUserCertificateModel(String username) {
+ for (UserCertificateModel ucm : list) {
+ if (ucm.user.username.equalsIgnoreCase(username)) {
+ return ucm;
+ }
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/gitblit/authority/UserCertificateModel.java b/src/main/java/com/gitblit/authority/UserCertificateModel.java
new file mode 100644
index 00000000..6c69a93b
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/UserCertificateModel.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2012 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.authority;
+
+import java.math.BigInteger;
+import java.security.cert.X509Certificate;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.eclipse.jgit.lib.Config;
+
+import com.gitblit.Constants;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.utils.X509Utils.RevocationReason;
+
+public class UserCertificateModel implements Comparable<UserCertificateModel> {
+ public UserModel user;
+ public Date expires;
+ public List<X509Certificate> certs;
+ public List<String> revoked;
+ public String notes;
+
+ public UserCertificateModel(UserModel user) {
+ this.user = user;
+ }
+
+ public void update(Config config) {
+ if (expires == null) {
+ config.unset("user", user.username, "expires");
+ } else {
+ SimpleDateFormat df = new SimpleDateFormat(Constants.ISO8601);
+ config.setString("user", user.username, "expires", df.format(expires));
+ }
+ if (StringUtils.isEmpty(notes)) {
+ config.unset("user", user.username, "notes");
+ } else {
+ config.setString("user", user.username, "notes", notes);
+ }
+ if (ArrayUtils.isEmpty(revoked)) {
+ config.unset("user", user.username, "revoked");
+ } else {
+ config.setStringList("user", user.username, "revoked", revoked);
+ }
+ }
+
+ @Override
+ public int compareTo(UserCertificateModel o) {
+ return user.compareTo(o.user);
+ }
+
+ public void revoke(BigInteger serial, RevocationReason reason) {
+ if (revoked == null) {
+ revoked = new ArrayList<String>();
+ }
+ revoked.add(serial.toString() + ":" + reason.ordinal());
+ expires = null;
+ for (X509Certificate cert : certs) {
+ if (!isRevoked(cert.getSerialNumber())) {
+ if (!isExpired(cert.getNotAfter())) {
+ if (expires == null || cert.getNotAfter().after(expires)) {
+ expires = cert.getNotAfter();
+ }
+ }
+ }
+ }
+ }
+
+ public boolean isRevoked(BigInteger serial) {
+ return isRevoked(serial.toString());
+ }
+
+ public boolean isRevoked(String serial) {
+ if (ArrayUtils.isEmpty(revoked)) {
+ return false;
+ }
+ String sn = serial + ":";
+ for (String s : revoked) {
+ if (s.startsWith(sn)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public RevocationReason getRevocationReason(BigInteger serial) {
+ try {
+ String sn = serial + ":";
+ for (String s : revoked) {
+ if (s.startsWith(sn)) {
+ String r = s.substring(sn.length());
+ int i = Integer.parseInt(r);
+ return RevocationReason.values()[i];
+ }
+ }
+ } catch (Exception e) {
+ }
+ return RevocationReason.unspecified;
+ }
+
+ public CertificateStatus getStatus() {
+ if (expires == null) {
+ return CertificateStatus.unknown;
+ } else if (isExpired(expires)) {
+ return CertificateStatus.expired;
+ } else if (isExpiring(expires)) {
+ return CertificateStatus.expiring;
+ }
+ return CertificateStatus.ok;
+ }
+
+ public boolean hasExpired() {
+ return expires != null && isExpiring(expires);
+ }
+
+ public CertificateStatus getStatus(X509Certificate cert) {
+ if (isRevoked(cert.getSerialNumber())) {
+ return CertificateStatus.revoked;
+ } else if (isExpired(cert.getNotAfter())) {
+ return CertificateStatus.expired;
+ } else if (isExpiring(cert.getNotAfter())) {
+ return CertificateStatus.expiring;
+ }
+ return CertificateStatus.ok;
+ }
+
+ private boolean isExpiring(Date date) {
+ return (date.getTime() - System.currentTimeMillis()) <= TimeUtils.ONEDAY * 30;
+ }
+
+ private boolean isExpired(Date date) {
+ return date.getTime() < System.currentTimeMillis();
+ }
+ } \ No newline at end of file
diff --git a/src/main/java/com/gitblit/authority/UserCertificatePanel.java b/src/main/java/com/gitblit/authority/UserCertificatePanel.java
new file mode 100644
index 00000000..0c49252c
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/UserCertificatePanel.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2012 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.authority;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.text.MessageFormat;
+import java.util.Date;
+
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableRowSorter;
+
+import com.gitblit.client.HeaderPanel;
+import com.gitblit.client.Translation;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.X509Utils.RevocationReason;
+import com.gitblit.utils.X509Utils.X509Metadata;
+
+public abstract class UserCertificatePanel extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private Frame owner;
+
+ private UserCertificateModel ucm;
+
+ private UserOidsPanel oidsPanel;
+
+ private CertificatesTableModel tableModel;
+
+ private JButton saveUserButton;
+
+ private JButton editUserButton;
+
+ private JButton newCertificateButton;
+
+ private JButton revokeCertificateButton;
+
+ private JTable table;
+
+ public UserCertificatePanel(Frame owner) {
+ super(new BorderLayout());
+
+ this.owner = owner;
+ oidsPanel = new UserOidsPanel();
+
+ JPanel fp = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
+ fp.add(oidsPanel, BorderLayout.NORTH);
+
+ JPanel fieldsPanel = new JPanel(new BorderLayout());
+ fieldsPanel.add(new HeaderPanel(Translation.get("gb.properties"), "vcard_16x16.png"), BorderLayout.NORTH);
+ fieldsPanel.add(fp, BorderLayout.CENTER);
+
+ saveUserButton = new JButton(Translation.get("gb.save"));
+ saveUserButton.setEnabled(false);
+ saveUserButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ setEditable(false);
+ String username = ucm.user.username;
+ oidsPanel.updateUser(ucm);
+ saveUser(username, ucm);
+ }
+ });
+
+ editUserButton = new JButton(Translation.get("gb.edit"));
+ editUserButton.setEnabled(false);
+ editUserButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ setEditable(true);
+ }
+ });
+
+ JPanel userControls = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ userControls.add(editUserButton);
+ userControls.add(saveUserButton);
+ fieldsPanel.add(userControls, BorderLayout.SOUTH);
+
+ JPanel certificatesPanel = new JPanel(new BorderLayout());
+ certificatesPanel.add(new HeaderPanel(Translation.get("gb.certificates"), "rosette_16x16.png"), BorderLayout.NORTH);
+ tableModel = new CertificatesTableModel();
+ table = Utils.newTable(tableModel, Utils.DATE_FORMAT);
+ table.setRowSorter(new TableRowSorter<CertificatesTableModel>(tableModel));
+ table.setDefaultRenderer(CertificateStatus.class, new CertificateStatusRenderer());
+ table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getValueIsAdjusting()) {
+ return;
+ }
+ boolean enable = false;
+ int row = table.getSelectedRow();
+ if (row > -1) {
+ int modelIndex = table.convertRowIndexToModel(row);
+ X509Certificate cert = tableModel.get(modelIndex);
+ enable = !ucm.isRevoked(cert.getSerialNumber());
+ }
+ revokeCertificateButton.setEnabled(enable);
+ }
+ });
+ table.addMouseListener(new MouseAdapter() {
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ int row = table.rowAtPoint(e.getPoint());
+ int modelIndex = table.convertRowIndexToModel(row);
+ X509Certificate cert = tableModel.get(modelIndex);
+ X509CertificateViewer viewer = new X509CertificateViewer(UserCertificatePanel.this.owner, cert);
+ viewer.setVisible(true);
+ }
+ }
+ });
+ certificatesPanel.add(new JScrollPane(table), BorderLayout.CENTER);
+
+ newCertificateButton = new JButton(Translation.get("gb.newCertificate"));
+ newCertificateButton.setEnabled(false);
+ newCertificateButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ try {
+ if (saveUserButton.isEnabled()) {
+ // save changes
+ String username = ucm.user.username;
+ setEditable(false);
+ oidsPanel.updateUser(ucm);
+ saveUser(username, ucm);
+ }
+
+ NewClientCertificateDialog dialog = new NewClientCertificateDialog(UserCertificatePanel.this.owner,
+ ucm.user.getDisplayName(), getDefaultExpiration(), isAllowEmail());
+ dialog.setModal(true);
+ dialog.setVisible(true);
+ if (dialog.isCanceled()) {
+ return;
+ }
+
+ final boolean sendEmail = dialog.sendEmail();
+ final UserModel user = ucm.user;
+ final X509Metadata metadata = new X509Metadata(user.username, dialog.getPassword());
+ metadata.userDisplayname = user.getDisplayName();
+ metadata.emailAddress = user.emailAddress;
+ metadata.passwordHint = dialog.getPasswordHint();
+ metadata.notAfter = dialog.getExpiration();
+
+ AuthorityWorker worker = new AuthorityWorker(UserCertificatePanel.this.owner) {
+ @Override
+ protected Boolean doRequest() throws IOException {
+ return newCertificate(ucm, metadata, sendEmail);
+ }
+
+ @Override
+ protected void onSuccess() {
+ JOptionPane.showMessageDialog(UserCertificatePanel.this.owner,
+ MessageFormat.format(Translation.get("gb.clientCertificateGenerated"), user.getDisplayName()),
+ Translation.get("gb.newCertificate"), JOptionPane.INFORMATION_MESSAGE);
+ }
+ };
+ worker.execute();
+ } catch (Exception x) {
+ Utils.showException(UserCertificatePanel.this, x);
+ }
+ }
+ });
+
+ revokeCertificateButton = new JButton(Translation.get("gb.revokeCertificate"));
+ revokeCertificateButton.setEnabled(false);
+ revokeCertificateButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ try {
+ int row = table.getSelectedRow();
+ if (row < 0) {
+ return;
+ }
+ int modelIndex = table.convertRowIndexToModel(row);
+ final X509Certificate cert = tableModel.get(modelIndex);
+
+ String [] choices = new String[RevocationReason.reasons.length];
+ for (int i = 0; i < choices.length; i++) {
+ choices[i] = Translation.get("gb." + RevocationReason.reasons[i].name());
+ }
+
+ Object choice = JOptionPane.showInputDialog(UserCertificatePanel.this.owner,
+ Translation.get("gb.revokeCertificateReason"), Translation.get("gb.revokeCertificate"),
+ JOptionPane.PLAIN_MESSAGE, new ImageIcon(getClass().getResource("/rosette_32x32.png")), choices, Translation.get("gb.unspecified"));
+ if (choice == null) {
+ return;
+ }
+ RevocationReason selection = RevocationReason.unspecified;
+ for (int i = 0 ; i < choices.length; i++) {
+ if (choices[i].equals(choice)) {
+ selection = RevocationReason.reasons[i];
+ break;
+ }
+ }
+ final RevocationReason reason = selection;
+ if (!ucm.isRevoked(cert.getSerialNumber())) {
+ if (ucm.certs.size() == 1) {
+ // no other certificates
+ ucm.expires = null;
+ } else {
+ // determine new expires date for user
+ Date newExpires = null;
+ for (X509Certificate c : ucm.certs) {
+ if (!c.equals(cert)) {
+ if (!ucm.isRevoked(c.getSerialNumber())) {
+ if (newExpires == null || c.getNotAfter().after(newExpires)) {
+ newExpires = c.getNotAfter();
+ }
+ }
+ }
+ }
+ ucm.expires = newExpires;
+ }
+
+ AuthorityWorker worker = new AuthorityWorker(UserCertificatePanel.this.owner) {
+
+ @Override
+ protected Boolean doRequest() throws IOException {
+ return revoke(ucm, cert, reason);
+ }
+
+ @Override
+ protected void onSuccess() {
+ JOptionPane.showMessageDialog(UserCertificatePanel.this.owner,
+ MessageFormat.format(Translation.get("gb.certificateRevoked"), cert.getSerialNumber(), cert.getIssuerDN().getName()),
+ Translation.get("gb.revokeCertificate"), JOptionPane.INFORMATION_MESSAGE);
+ }
+
+ };
+ worker.execute();
+ }
+ } catch (Exception x) {
+ Utils.showException(UserCertificatePanel.this, x);
+ }
+ }
+ });
+
+ JPanel certificateControls = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ certificateControls.add(newCertificateButton);
+ certificateControls.add(revokeCertificateButton);
+ certificatesPanel.add(certificateControls, BorderLayout.SOUTH);
+
+ add(fieldsPanel, BorderLayout.NORTH);
+ add(certificatesPanel, BorderLayout.CENTER);
+ setEditable(false);
+ }
+
+ public void setUserCertificateModel(UserCertificateModel ucm) {
+ this.ucm = ucm;
+ setEditable(false);
+ oidsPanel.setUserCertificateModel(ucm);
+
+ tableModel.setUserCertificateModel(ucm);
+ tableModel.fireTableDataChanged();
+ Utils.packColumns(table, Utils.MARGIN);
+ }
+
+ public void setEditable(boolean editable) {
+ oidsPanel.setEditable(editable);
+
+ editUserButton.setEnabled(!editable && ucm != null);
+ saveUserButton.setEnabled(editable && ucm != null);
+
+ newCertificateButton.setEnabled(ucm != null);
+ revokeCertificateButton.setEnabled(false);
+ }
+
+ public abstract Date getDefaultExpiration();
+ public abstract boolean isAllowEmail();
+
+ public abstract boolean saveUser(String username, UserCertificateModel ucm);
+ public abstract boolean newCertificate(UserCertificateModel ucm, X509Metadata metadata, boolean sendEmail);
+ public abstract boolean revoke(UserCertificateModel ucm, X509Certificate cert, RevocationReason reason);
+}
diff --git a/src/main/java/com/gitblit/authority/UserCertificateTableModel.java b/src/main/java/com/gitblit/authority/UserCertificateTableModel.java
new file mode 100644
index 00000000..dde73fc0
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/UserCertificateTableModel.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2012 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.authority;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import javax.swing.table.AbstractTableModel;
+
+import com.gitblit.client.Translation;
+
+/**
+ * Table model of a list of user certificate models.
+ *
+ * @author James Moger
+ *
+ */
+public class UserCertificateTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+
+ List<UserCertificateModel> list;
+
+ enum Columns {
+ Username, DisplayName, Status, Expires;
+
+ @Override
+ public String toString() {
+ return name().replace('_', ' ');
+ }
+ }
+
+ public UserCertificateTableModel() {
+ this(new ArrayList<UserCertificateModel>());
+ }
+
+ public UserCertificateTableModel(List<UserCertificateModel> list) {
+ this.list = list;
+ Collections.sort(this.list);
+ }
+
+ @Override
+ public int getRowCount() {
+ return list.size();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return Columns.values().length;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ Columns col = Columns.values()[column];
+ switch (col) {
+ case Username:
+ return Translation.get("gb.username");
+ case DisplayName:
+ return Translation.get("gb.displayName");
+ case Expires:
+ return Translation.get("gb.expires");
+ case Status:
+ return Translation.get("gb.status");
+ }
+ 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) {
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ case Expires:
+ return Date.class;
+ case Status:
+ return CertificateStatus.class;
+ default:
+ return String.class;
+ }
+ }
+
+ @Override
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ UserCertificateModel model = list.get(rowIndex);
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ case Username:
+ return model.user.username;
+ case DisplayName:
+ return model.user.getDisplayName();
+ case Expires:
+ return model.expires;
+ case Status:
+ return model.getStatus();
+ }
+ return null;
+ }
+
+ public UserCertificateModel get(int modelRow) {
+ return list.get(modelRow);
+ }
+}
diff --git a/src/main/java/com/gitblit/authority/UserOidsPanel.java b/src/main/java/com/gitblit/authority/UserOidsPanel.java
new file mode 100644
index 00000000..8c3adf66
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/UserOidsPanel.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012 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.authority;
+
+import java.awt.GridLayout;
+
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import com.gitblit.client.Translation;
+
+public class UserOidsPanel extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private JTextField displayname;
+ private JTextField username;
+ private JTextField emailAddress;
+ private JTextField organizationalUnit;
+ private JTextField organization;
+ private JTextField locality;
+ private JTextField stateProvince;
+ private JTextField countryCode;
+
+ public UserOidsPanel() {
+ super();
+
+ displayname = new JTextField(20);
+ username = new JTextField(20);
+ username.setEditable(false);
+ emailAddress = new JTextField(20);
+ organizationalUnit = new JTextField(20);
+ organization = new JTextField(20);
+ locality = new JTextField(20);
+ stateProvince = new JTextField(20);
+ countryCode = new JTextField(20);
+
+ setLayout(new GridLayout(0, 1, Utils.MARGIN, Utils.MARGIN));
+ add(Utils.newFieldPanel(Translation.get("gb.displayName"), displayname));
+ add(Utils.newFieldPanel(Translation.get("gb.username") + " (CN)", username));
+ add(Utils.newFieldPanel(Translation.get("gb.emailAddress") + " (E)", emailAddress));
+ add(Utils.newFieldPanel(Translation.get("gb.organizationalUnit") + " (OU)", organizationalUnit));
+ add(Utils.newFieldPanel(Translation.get("gb.organization") + " (O)", organization));
+ add(Utils.newFieldPanel(Translation.get("gb.locality") + " (L)", locality));
+ add(Utils.newFieldPanel(Translation.get("gb.stateProvince") + " (ST)", stateProvince));
+ add(Utils.newFieldPanel(Translation.get("gb.countryCode") + " (C)", countryCode));
+ }
+
+ public void setUserCertificateModel(UserCertificateModel ucm) {
+ setEditable(false);
+ displayname.setText(ucm.user.getDisplayName());
+ username.setText(ucm.user.username);
+ emailAddress.setText(ucm.user.emailAddress);
+ organizationalUnit.setText(ucm.user.organizationalUnit);
+ organization.setText(ucm.user.organization);
+ locality.setText(ucm.user.locality);
+ stateProvince.setText(ucm.user.stateProvince);
+ countryCode.setText(ucm.user.countryCode);
+ }
+
+ public void setEditable(boolean editable) {
+ displayname.setEditable(editable);
+// username.setEditable(editable);
+ emailAddress.setEditable(editable);
+ organizationalUnit.setEditable(editable);
+ organization.setEditable(editable);
+ locality.setEditable(editable);
+ stateProvince.setEditable(editable);
+ countryCode.setEditable(editable);
+ }
+
+ protected void updateUser(UserCertificateModel ucm) {
+ ucm.user.displayName = displayname.getText();
+ ucm.user.username = username.getText();
+ ucm.user.emailAddress = emailAddress.getText();
+ ucm.user.organizationalUnit = organizationalUnit.getText();
+ ucm.user.organization = organization.getText();
+ ucm.user.locality = locality.getText();
+ ucm.user.stateProvince = stateProvince.getText();
+ ucm.user.countryCode = countryCode.getText();
+ }
+}
diff --git a/src/main/java/com/gitblit/authority/Utils.java b/src/main/java/com/gitblit/authority/Utils.java
new file mode 100644
index 00000000..45e028e7
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/Utils.java
@@ -0,0 +1,123 @@
+package com.gitblit.authority;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.Insets;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Date;
+
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableModel;
+
+import com.gitblit.client.DateCellRenderer;
+import com.gitblit.client.Translation;
+import com.gitblit.utils.StringUtils;
+
+public class Utils {
+
+ public final static int LABEL_WIDTH = 175;
+
+ public final static int MARGIN = 5;
+
+ public final static Insets INSETS = new Insets(MARGIN, MARGIN, MARGIN, MARGIN);
+
+ public final static String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm";
+
+ public final static String DATE_FORMAT = "yyyy-MM-dd";
+
+ public static JTable newTable(TableModel model, String datePattern) {
+ JTable table = new JTable(model);
+ table.setRowHeight(table.getFont().getSize() + 8);
+ table.setCellSelectionEnabled(false);
+ table.setRowSelectionAllowed(true);
+ table.getTableHeader().setReorderingAllowed(false);
+ table.setGridColor(new Color(0xd9d9d9));
+ table.setBackground(Color.white);
+ table.setDefaultRenderer(Date.class,
+ new DateCellRenderer(datePattern, Color.orange.darker()));
+ return table;
+ }
+
+ public static JPanel newFieldPanel(String label, Component c) {
+ return newFieldPanel(label, c, null);
+ }
+
+ public static JPanel newFieldPanel(String label, Component c, String trailingLabel) {
+ JLabel jlabel = new JLabel(label);
+ jlabel.setPreferredSize(new Dimension(Utils.LABEL_WIDTH, 20));
+ JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ panel.add(jlabel);
+ panel.add(c);
+ if (!StringUtils.isEmpty(trailingLabel)) {
+ panel.add(new JLabel(trailingLabel));
+ }
+ return panel;
+ }
+
+ public static void showException(Component c, Throwable t) {
+ StringWriter writer = new StringWriter();
+ t.printStackTrace(new PrintWriter(writer));
+ String stacktrace = writer.toString();
+ try {
+ writer.close();
+ } catch (Throwable x) {
+ }
+ JTextArea textArea = new JTextArea(stacktrace);
+ textArea.setFont(new Font("monospaced", Font.PLAIN, 11));
+ JScrollPane jsp = new JScrollPane(textArea);
+ jsp.setPreferredSize(new Dimension(800, 400));
+ JOptionPane.showMessageDialog(c, jsp, Translation.get("gb.error"),
+ JOptionPane.ERROR_MESSAGE);
+ }
+
+ public static void packColumns(JTable table, int margin) {
+ for (int c = 0; c < table.getColumnCount(); c++) {
+ packColumn(table, c, 4);
+ }
+ }
+
+ // Sets the preferred width of the visible column specified by vColIndex.
+ // The column will be just wide enough to show the column head and the
+ // widest cell in the column. margin pixels are added to the left and right
+ // (resulting in an additional width of 2*margin pixels).
+ private static void packColumn(JTable table, int vColIndex, int margin) {
+ DefaultTableColumnModel colModel = (DefaultTableColumnModel) table.getColumnModel();
+ TableColumn col = colModel.getColumn(vColIndex);
+ int width = 0;
+
+ // Get width of column header
+ TableCellRenderer renderer = col.getHeaderRenderer();
+ if (renderer == null) {
+ renderer = table.getTableHeader().getDefaultRenderer();
+ }
+ Component comp = renderer.getTableCellRendererComponent(table, col.getHeaderValue(), false,
+ false, 0, 0);
+ width = comp.getPreferredSize().width;
+
+ // Get maximum width of column data
+ for (int r = 0; r < table.getRowCount(); r++) {
+ renderer = table.getCellRenderer(r, vColIndex);
+ comp = renderer.getTableCellRendererComponent(table, table.getValueAt(r, vColIndex),
+ false, false, r, vColIndex);
+ width = Math.max(width, comp.getPreferredSize().width);
+ }
+
+ // Add margin
+ width += 2 * margin;
+
+ // Set the width
+ col.setPreferredWidth(width);
+ }
+}
diff --git a/src/main/java/com/gitblit/authority/X509CertificateViewer.java b/src/main/java/com/gitblit/authority/X509CertificateViewer.java
new file mode 100644
index 00000000..797b9a81
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/X509CertificateViewer.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2012 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.authority;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.text.DateFormat;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+
+import com.gitblit.client.HeaderPanel;
+import com.gitblit.client.Translation;
+import com.gitblit.utils.StringUtils;
+
+public class X509CertificateViewer extends JDialog {
+
+ private static final long serialVersionUID = 1L;
+
+ public X509CertificateViewer(Frame owner, X509Certificate cert) {
+ super(owner);
+
+ setTitle(Translation.get("gb.viewCertificate"));
+
+ JPanel content = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN)) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Insets getInsets() {
+
+ return Utils.INSETS;
+ }
+ };
+
+ DateFormat df = DateFormat.getDateTimeInstance();
+
+ int l1 = 15;
+ int l2 = 25;
+ int l3 = 45;
+ JPanel panel = new JPanel(new GridLayout(0, 1, 0, 2*Utils.MARGIN));
+ panel.add(newField(Translation.get("gb.version"), "" + cert.getVersion(), 3));
+ panel.add(newField(Translation.get("gb.subject"), cert.getSubjectDN().getName(), l3));
+ panel.add(newField(Translation.get("gb.issuer"), cert.getIssuerDN().getName(), l3));
+ panel.add(newField(Translation.get("gb.serialNumber"), "0x" + cert.getSerialNumber().toString(16), l2));
+ panel.add(newField(Translation.get("gb.serialNumber"), cert.getSerialNumber().toString(), l2));
+ panel.add(newField(Translation.get("gb.validFrom"), df.format(cert.getNotBefore()), l2));
+ panel.add(newField(Translation.get("gb.validUntil"), df.format(cert.getNotAfter()), l2));
+ panel.add(newField(Translation.get("gb.publicKey"), cert.getPublicKey().getAlgorithm(), l1));
+ panel.add(newField(Translation.get("gb.signatureAlgorithm"), cert.getSigAlgName(), l1));
+ try {
+ panel.add(newField(Translation.get("gb.sha1FingerPrint"), fingerprint(StringUtils.getSHA1(cert.getEncoded())), l3));
+ } catch (CertificateEncodingException e1) {
+ }
+ try {
+ panel.add(newField(Translation.get("gb.md5FingerPrint"), fingerprint(StringUtils.getMD5(cert.getEncoded())), l3));
+ } catch (CertificateEncodingException e1) {
+ }
+
+ content.add(panel, BorderLayout.CENTER);
+
+ JButton ok = new JButton(Translation.get("gb.ok"));
+ ok.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ setVisible(false);
+ }
+ });
+
+ JPanel controls = new JPanel();
+ controls.add(ok);
+
+ content.add(controls, BorderLayout.SOUTH);
+
+ getContentPane().add(new HeaderPanel(Translation.get("gb.certificate"), "rosette_16x16.png"), BorderLayout.NORTH);
+ getContentPane().add(content, BorderLayout.CENTER);
+ pack();
+
+ setLocationRelativeTo(owner);
+ }
+
+ private JPanel newField(String label, String value, int cols) {
+ JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 2*Utils.MARGIN, 0));
+ JLabel lbl = new JLabel(label);
+ lbl.setHorizontalAlignment(SwingConstants.RIGHT);
+ lbl.setPreferredSize(new Dimension(125, 20));
+ panel.add(lbl);
+ JTextField tf = new JTextField(value, cols);
+ tf.setCaretPosition(0);
+ tf.setEditable(false);
+ panel.add(tf);
+ return panel;
+ }
+
+ private String fingerprint(String value) {
+ value = value.toUpperCase();
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < value.length(); i += 2) {
+ sb.append(value.charAt(i));
+ sb.append(value.charAt(i + 1));
+ sb.append(':');
+ }
+ sb.setLength(sb.length() - 1);
+ return sb.toString();
+ }
+}