]> source.dussan.org Git - gitblit.git/commitdiff
X509 certificate authentication based on Kevin Anderson's implementation
authorJames Moger <james.moger@gitblit.com>
Fri, 23 Nov 2012 15:17:51 +0000 (10:17 -0500)
committerJames Moger <james.moger@gitblit.com>
Fri, 23 Nov 2012 15:17:51 +0000 (10:17 -0500)
distrib/gitblit.properties
docs/04_releases.mkd
src/com/gitblit/AuthenticationFilter.java
src/com/gitblit/GitBlit.java
src/com/gitblit/GitFilter.java
src/com/gitblit/authority/GitblitAuthority.java [new file with mode: 0644]
src/com/gitblit/utils/HttpUtils.java
src/com/gitblit/wicket/pages/BasePage.java

index 1a5a61b3dc63324f8772e741c202eb0d44007f6b..1562c51397b6109e78827a82919f23acd7e38e4d 100644 (file)
@@ -69,6 +69,28 @@ git.submoduleUrlPatterns = .*?://github.com/(.*)
 # SINCE 0.5.0\r
 git.enableGitServlet = true\r
 \r
+# If you want to restrict all git servlet access to those with valid X509 client\r
+# certificates then set this value to true.\r
+#\r
+# SINCE 1.2.0\r
+git.requiresClientCertificate = false\r
+\r
+# Enforce date checks on client certificates to ensure that they are not being\r
+# used prematurely and that they have not expired.\r
+#\r
+# SINCE 1.2.0\r
+git.enforceCertificateValidity = true\r
+\r
+# List of OIDs to extract from a client certificate DN to map a certificate to\r
+# an account username.\r
+#\r
+# e.g. git.certificateUsernameOIDs = CN\r
+# e.g. git.certificateUsernameOIDs = FirstName LastName\r
+#\r
+# SPACE-DELIMITED\r
+# SINCE 1.2.0\r
+git.certificateUsernameOIDs = CN\r
+\r
 # Only serve/display bare repositories.\r
 # If there are non-bare repositories in git.repositoriesFolder and this setting\r
 # is true, they will be excluded from the ui. \r
index d0d24fe710fac9e1ee522ece7f467ff3811b117a..b8fa5f543f68030118ac7aab8c27d21bdef498c8 100644 (file)
@@ -47,6 +47,11 @@ In order to fork a repository, the user account must have the *fork* permission
     **New:** *git.garbageCollectionHour = 0*  \r
     **New:** *git.defaultGarbageCollectionThreshold = 500k*  \r
     **New:** *git.defaultGarbageCollectionPeriod = 7 days*\r
+- Added support for X509 client certificate authentication (github/kevinanderson1).  \r
+You can require all git servlet access be authenticated by a client certificate.  You may also specify the OID fingerprint to use for mapping a certificate to a username.  It should be noted that the user account MUST already exist in Gitblit for this authentication mechanism to work; this mechanism can not be used to automatically create user accounts from a certificate.  \r
+    **New:** *git.requireClientCertificates = false*  \r
+    **New:** *git.enforceCertificateValidity = true*  \r
+    **New:** *git.certificateUsernameOIDs = CN*\r
 - Added setting to control length of shortened commit ids  \r
     **New:** *web.shortCommitIdLength=8*  \r
 - Added simple project pages.  A project is a subfolder off the *git.repositoriesFolder*.\r
index 4762c428f4a5baf7d87f84fb71eb3d566bf2262b..64aa44111e452380cf5051308a8be22fe8f45c8d 100644 (file)
@@ -69,6 +69,15 @@ public abstract class AuthenticationFilter implements Filter {
        @Override\r
        public abstract void doFilter(final ServletRequest request, final ServletResponse response,\r
                        final FilterChain chain) throws IOException, ServletException;\r
+       \r
+       /**\r
+        * Allow the filter to require a client certificate to continue processing.\r
+        * \r
+        * @return true, if a client certificate is required\r
+        */\r
+       protected boolean requiresClientCertificate() {\r
+               return false;\r
+       }\r
 \r
        /**\r
         * Returns the full relative url of the request.\r
@@ -95,6 +104,16 @@ public abstract class AuthenticationFilter implements Filter {
         */\r
        protected UserModel getUser(HttpServletRequest httpRequest) {\r
                UserModel user = null;\r
+               // try request authentication\r
+               user = GitBlit.self().authenticate(httpRequest);\r
+               if (user != null) {\r
+                       return user;\r
+               } else if (requiresClientCertificate()) {\r
+                       // http request does not have a valid certificate\r
+                       // and the filter requires one\r
+                       return null;\r
+               }\r
+               \r
                // look for client authorization credentials in header\r
                final String authorization = httpRequest.getHeader("Authorization");\r
                if (authorization != null && authorization.startsWith(BASIC)) {\r
index b35bf507e6b000bec400d4574e12dea3b9020d85..612870b6c288cf4392eba9830c76b2b415ad9e1b 100644 (file)
@@ -100,6 +100,7 @@ import com.gitblit.utils.ByteFormat;
 import com.gitblit.utils.ContainerUtils;\r
 import com.gitblit.utils.DeepCopier;\r
 import com.gitblit.utils.FederationUtils;\r
+import com.gitblit.utils.HttpUtils;\r
 import com.gitblit.utils.JGitUtils;\r
 import com.gitblit.utils.JsonUtils;\r
 import com.gitblit.utils.MetricUtils;\r
@@ -561,6 +562,14 @@ public class GitBlit implements ServletContextListener {
         * @return a user object or null\r
         */\r
        public UserModel authenticate(HttpServletRequest httpRequest) {\r
+               boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true);\r
+               String [] oids = getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]);\r
+               UserModel model = HttpUtils.getUserModelFromCertificate(httpRequest, checkValidity, oids);\r
+               if (model != null) {\r
+                       UserModel user = GitBlit.self().getUserModel(model.username);\r
+                       logger.info("{0} authenticated by client certificate from {1}", user.username, httpRequest.getRemoteAddr());\r
+                       return user;\r
+               }\r
                return null;\r
        }\r
 \r
index 6afdb01419f9917a5e29a3901feb020bf115fd8c..2b769d4b32aeaa4ccacc4f7b3dc91fddc343636e 100644 (file)
@@ -124,6 +124,11 @@ public class GitFilter extends AccessRestrictionFilter {
                return true;\r
        }\r
 \r
+       @Override\r
+       protected boolean requiresClientCertificate() {\r
+               return GitBlit.getBoolean(Keys.git.requiresClientCertificate, false);\r
+       }\r
+\r
        /**\r
         * Determine if the repository requires authentication.\r
         * \r
diff --git a/src/com/gitblit/authority/GitblitAuthority.java b/src/com/gitblit/authority/GitblitAuthority.java
new file mode 100644 (file)
index 0000000..ff48ecf
--- /dev/null
@@ -0,0 +1,547 @@
+/*\r
+ * Copyright 2012 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.authority;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Container;\r
+import java.awt.Dimension;\r
+import java.awt.EventQueue;\r
+import java.awt.FlowLayout;\r
+import java.awt.Insets;\r
+import java.awt.Point;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.awt.event.KeyAdapter;\r
+import java.awt.event.KeyEvent;\r
+import java.awt.event.WindowAdapter;\r
+import java.awt.event.WindowEvent;\r
+import java.io.BufferedInputStream;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.FilenameFilter;\r
+import java.io.IOException;\r
+import java.security.cert.CertificateFactory;\r
+import java.security.cert.X509Certificate;\r
+import java.text.MessageFormat;\r
+import java.util.ArrayList;\r
+import java.util.Calendar;\r
+import java.util.Collections;\r
+import java.util.Date;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
+import javax.activation.DataHandler;\r
+import javax.activation.FileDataSource;\r
+import javax.mail.Message;\r
+import javax.mail.Multipart;\r
+import javax.mail.internet.MimeBodyPart;\r
+import javax.mail.internet.MimeMultipart;\r
+import javax.swing.ImageIcon;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JOptionPane;\r
+import javax.swing.JPanel;\r
+import javax.swing.JScrollPane;\r
+import javax.swing.JSplitPane;\r
+import javax.swing.JTable;\r
+import javax.swing.JTextField;\r
+import javax.swing.RowFilter;\r
+import javax.swing.UIManager;\r
+import javax.swing.event.ListSelectionEvent;\r
+import javax.swing.event.ListSelectionListener;\r
+import javax.swing.table.TableRowSorter;\r
+\r
+import org.eclipse.jgit.errors.ConfigInvalidException;\r
+import org.eclipse.jgit.lib.StoredConfig;\r
+import org.eclipse.jgit.storage.file.FileBasedConfig;\r
+import org.eclipse.jgit.util.FS;\r
+\r
+import com.gitblit.ConfigUserService;\r
+import com.gitblit.Constants;\r
+import com.gitblit.FileSettings;\r
+import com.gitblit.IStoredSettings;\r
+import com.gitblit.IUserService;\r
+import com.gitblit.Keys;\r
+import com.gitblit.MailExecutor;\r
+import com.gitblit.client.HeaderPanel;\r
+import com.gitblit.client.Translation;\r
+import com.gitblit.models.UserModel;\r
+import com.gitblit.utils.StringUtils;\r
+import com.gitblit.utils.X509Utils;\r
+import com.gitblit.utils.X509Utils.RevocationReason;\r
+import com.gitblit.utils.X509Utils.X509Metadata;\r
+\r
+/**\r
+ * Simple GUI tool for administering Gitblit client certificates.\r
+ * \r
+ * @author James Moger\r
+ *\r
+ */\r
+public class GitblitAuthority extends JFrame {\r
+\r
+       private static final long serialVersionUID = 1L;\r
+       \r
+       private final UserCertificateTableModel tableModel;\r
+\r
+       private UserCertificatePanel userCertificatePanel;\r
+       \r
+       private File folder;\r
+       \r
+       private IStoredSettings gitblitSettings;\r
+       \r
+       private IUserService userService;\r
+       \r
+       private String caKeystorePassword = null;\r
+\r
+       private JTable table;\r
+       \r
+       private int defaultDuration;\r
+       \r
+       private TableRowSorter<UserCertificateTableModel> defaultSorter;\r
+\r
+       public static void main(String... args) {\r
+               EventQueue.invokeLater(new Runnable() {\r
+                       public void run() {\r
+                               try {\r
+                                       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());\r
+                               } catch (Exception e) {\r
+                               }\r
+                               GitblitAuthority authority = new GitblitAuthority();\r
+                               authority.initialize();\r
+                               authority.setLocationRelativeTo(null);\r
+                               authority.setVisible(true);\r
+                       }\r
+               });\r
+       }\r
+\r
+       public GitblitAuthority() {\r
+               super();\r
+               tableModel = new UserCertificateTableModel();\r
+               defaultSorter = new TableRowSorter<UserCertificateTableModel>(tableModel);\r
+       }\r
+       \r
+       public void initialize() {\r
+               setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage());\r
+               setTitle("Gitblit PKI Authority v" + Constants.VERSION + " (" + Constants.VERSION_DATE + ")");\r
+               setContentPane(getUI());\r
+               setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);\r
+               addWindowListener(new WindowAdapter() {\r
+                       @Override\r
+                       public void windowClosing(WindowEvent event) {\r
+                               saveSizeAndPosition();\r
+                       }\r
+\r
+                       @Override\r
+                       public void windowOpened(WindowEvent event) {\r
+                       }\r
+               });             \r
+\r
+               setSizeAndPosition();\r
+               \r
+               File folder = new File(System.getProperty("user.dir"));\r
+               load(folder);\r
+       }\r
+       \r
+       private void setSizeAndPosition() {\r
+               String sz = null;\r
+               String pos = null;\r
+               try {\r
+                       StoredConfig config = getConfig();\r
+                       sz = config.getString("ui", null, "size");\r
+                       pos = config.getString("ui", null, "position");\r
+                       defaultDuration = config.getInt("new",  "duration", 365);\r
+               } catch (Throwable t) {\r
+                       t.printStackTrace();\r
+               }\r
+\r
+               // try to restore saved window size\r
+               if (StringUtils.isEmpty(sz)) {\r
+                       setSize(850, 500);\r
+               } else {\r
+                       String[] chunks = sz.split("x");\r
+                       int width = Integer.parseInt(chunks[0]);\r
+                       int height = Integer.parseInt(chunks[1]);\r
+                       setSize(width, height);\r
+               }\r
+\r
+               // try to restore saved window position\r
+               if (StringUtils.isEmpty(pos)) {\r
+                       setLocationRelativeTo(null);\r
+               } else {\r
+                       String[] chunks = pos.split(",");\r
+                       int x = Integer.parseInt(chunks[0]);\r
+                       int y = Integer.parseInt(chunks[1]);\r
+                       setLocation(x, y);\r
+               }\r
+       }\r
+\r
+       private void saveSizeAndPosition() {\r
+               try {\r
+                       // save window size and position\r
+                       StoredConfig config = getConfig();\r
+                       Dimension sz = GitblitAuthority.this.getSize();\r
+                       config.setString("ui", null, "size",\r
+                                       MessageFormat.format("{0,number,0}x{1,number,0}", sz.width, sz.height));\r
+                       Point pos = GitblitAuthority.this.getLocationOnScreen();\r
+                       config.setString("ui", null, "position",\r
+                                       MessageFormat.format("{0,number,0},{1,number,0}", pos.x, pos.y));\r
+                       config.save();\r
+               } catch (Throwable t) {\r
+                       Utils.showException(GitblitAuthority.this, t);\r
+               }\r
+       }\r
+       \r
+       private StoredConfig getConfig() throws IOException, ConfigInvalidException {\r
+               File configFile  = new File(System.getProperty("user.dir"), X509Utils.CA_CONFIG);\r
+               FileBasedConfig config = new FileBasedConfig(configFile, FS.detect());\r
+               config.load();\r
+               return config;\r
+       }\r
+       \r
+       private IUserService loadUsers(File folder) {\r
+               File file = new File(folder, "gitblit.properties");\r
+               if (!file.exists()) {\r
+                       return null;\r
+               }\r
+               gitblitSettings = new FileSettings(file.getAbsolutePath());\r
+               caKeystorePassword = gitblitSettings.getString(Keys.server.storePassword, null);\r
+               String us = gitblitSettings.getString(Keys.realm.userService, "users.conf");\r
+               String ext = us.substring(us.lastIndexOf(".") + 1).toLowerCase();\r
+               IUserService service = null;\r
+               if (!ext.equals("conf") && !ext.equals("properties")) {\r
+                       if (us.equals("com.gitblit.LdapUserService")) {\r
+                               us = gitblitSettings.getString(Keys.realm.ldap.backingUserService, "users.conf");               \r
+                       } else if (us.equals("com.gitblit.LdapUserService")) {\r
+                               us = gitblitSettings.getString(Keys.realm.redmine.backingUserService, "users.conf");\r
+                       }\r
+               }\r
+\r
+               if (us.endsWith(".conf")) {\r
+                       service = new ConfigUserService(new File(us));\r
+               } else {\r
+                       throw new RuntimeException("Unsupported user service: " + us);\r
+               }\r
+               \r
+               service = new ConfigUserService(new File(us));\r
+               return service;\r
+       }\r
+       \r
+       private void load(File folder) {\r
+               this.folder = folder;\r
+               this.userService = loadUsers(folder);\r
+               if (userService != null) {\r
+                       // build empty certificate model for all users\r
+                       Map<String, UserCertificateModel> map = new HashMap<String, UserCertificateModel>();\r
+                       for (String user : userService.getAllUsernames()) {\r
+                               UserModel model = userService.getUserModel(user);\r
+                               UserCertificateModel ucm = new UserCertificateModel(model);                             \r
+                               map.put(user, ucm);\r
+                       }\r
+                       File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);\r
+                       FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());\r
+                       if (certificatesConfigFile.exists()) {\r
+                               try {\r
+                                       config.load();\r
+                                       // replace user certificate model with actual data\r
+                                       List<UserCertificateModel> list = UserCertificateConfig.KEY.parse(config).list;                                 \r
+                                       for (UserCertificateModel ucm : list) {                                         \r
+                                               ucm.user = userService.getUserModel(ucm.user.username);\r
+                                               map.put(ucm.user.username, ucm);\r
+                                       }\r
+                               } catch (IOException e) {\r
+                                       e.printStackTrace();\r
+                               } catch (ConfigInvalidException e) {\r
+                                       e.printStackTrace();\r
+                               }\r
+                       }\r
+                       \r
+                       tableModel.list = new ArrayList<UserCertificateModel>(map.values());\r
+                       Collections.sort(tableModel.list);\r
+                       tableModel.fireTableDataChanged();\r
+               }\r
+       }\r
+       \r
+       private List<X509Certificate> findCerts(File folder, String username) {\r
+               List<X509Certificate> list = new ArrayList<X509Certificate>();\r
+               File userFolder = new File(folder, X509Utils.CERTS + File.separator + username);\r
+               if (!userFolder.exists()) {\r
+                       return list;\r
+               }\r
+               File [] certs = userFolder.listFiles(new FilenameFilter() {\r
+                       @Override\r
+                       public boolean accept(File dir, String name) {\r
+                               return name.toLowerCase().endsWith(".cer") || name.toLowerCase().endsWith(".crt");\r
+                       }\r
+               });\r
+               try {\r
+                       CertificateFactory factory = CertificateFactory.getInstance("X.509");\r
+                       for (File cert : certs) {                               \r
+                               BufferedInputStream is = new BufferedInputStream(new FileInputStream(cert));\r
+                               X509Certificate x509 = (X509Certificate) factory.generateCertificate(is);\r
+                               is.close();\r
+                               list.add(x509);\r
+                       }\r
+               } catch (Exception e) {\r
+                       Utils.showException(GitblitAuthority.this, e);\r
+               }\r
+               return list;\r
+       }\r
+       \r
+       private Container getUI() {             \r
+               userCertificatePanel = new UserCertificatePanel(this) {\r
+                       \r
+                       private static final long serialVersionUID = 1L;\r
+                       @Override\r
+                       public Insets getInsets() {\r
+                               return Utils.INSETS;\r
+                       }\r
+\r
+                       @Override\r
+                       public Date getDefaultExpiration() {\r
+                               Calendar c = Calendar.getInstance();\r
+                               c.add(Calendar.DATE, defaultDuration);\r
+                               c.set(Calendar.HOUR_OF_DAY, 0);\r
+                               c.set(Calendar.MINUTE, 0);\r
+                               c.set(Calendar.SECOND, 0);\r
+                               c.set(Calendar.MILLISECOND, 0);\r
+                               return c.getTime();\r
+                       }\r
+                       \r
+                       @Override\r
+                       public void saveUser(String username, UserCertificateModel ucm) {\r
+                               userService.updateUserModel(username, ucm.user);\r
+                       }\r
+                       \r
+                       @Override\r
+                       public void newCertificate(UserCertificateModel ucm, X509Metadata metadata, boolean sendEmail) {\r
+                               Date notAfter = metadata.notAfter;\r
+                               metadata.serverHostname = gitblitSettings.getString(Keys.web.siteName, "localhost");\r
+                               UserModel user = ucm.user;                              \r
+                               \r
+                               // set default values from config file\r
+                               File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);\r
+                               FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());\r
+                               if (certificatesConfigFile.exists()) {\r
+                                       try {\r
+                                               config.load();\r
+                                       } catch (Exception e) {\r
+                                               Utils.showException(GitblitAuthority.this, e);\r
+                                       }\r
+                                       NewCertificateConfig certificateConfig = NewCertificateConfig.KEY.parse(config);\r
+                                       certificateConfig.update(metadata);\r
+                               }\r
+                               \r
+                               // restore expiration date\r
+                               metadata.notAfter = notAfter;\r
+                               \r
+                               // set user's specified OID values\r
+                               if (!StringUtils.isEmpty(user.organizationalUnit)) {\r
+                                       metadata.oids.put("OU", user.organizationalUnit);\r
+                               }\r
+                               if (!StringUtils.isEmpty(user.organization)) {\r
+                                       metadata.oids.put("O", user.organization);\r
+                               }\r
+                               if (!StringUtils.isEmpty(user.locality)) {\r
+                                       metadata.oids.put("L", user.locality);\r
+                               }\r
+                               if (!StringUtils.isEmpty(user.stateProvince)) {\r
+                                       metadata.oids.put("ST", user.stateProvince);\r
+                               }\r
+                               if (!StringUtils.isEmpty(user.countryCode)) {\r
+                                       metadata.oids.put("C", user.countryCode);\r
+                               }\r
+\r
+                               File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE);\r
+                               File zip = X509Utils.newClientBundle(metadata, caKeystoreFile, caKeystorePassword);\r
+                               \r
+                               // save latest expiration date\r
+                               if (ucm.expires == null || metadata.notAfter.after(ucm.expires)) {\r
+                                       ucm.expires = metadata.notAfter;\r
+                               }\r
+                               ucm.update(config);\r
+                               try {\r
+                                       config.save();\r
+                               } catch (Exception e) {\r
+                                       Utils.showException(GitblitAuthority.this, e);\r
+                               }\r
+                               \r
+                               // refresh user\r
+                               ucm.certs = null;\r
+                               int modelIndex = table.convertRowIndexToModel(table.getSelectedRow());\r
+                               tableModel.fireTableDataChanged();\r
+                               table.getSelectionModel().setSelectionInterval(modelIndex, modelIndex);\r
+                               \r
+                               if (sendEmail) {\r
+                                       // send email\r
+                                       try {\r
+                                               MailExecutor mail = new MailExecutor(gitblitSettings);\r
+                                               if (mail.isReady()) {\r
+                                                       Message message = mail.createMessage(user.emailAddress);\r
+                                                       message.setSubject("Your Gitblit client certificate for " + metadata.serverHostname);\r
+\r
+                                                       // body of email\r
+                                                       String body = X509Utils.processTemplate(new File(caKeystoreFile.getParentFile(), "mail.tmpl"), metadata);\r
+                                                       if (StringUtils.isEmpty(body)) {\r
+                                                               body = MessageFormat.format("Hi {0}\n\nHere is your client certificate bundle.\nInside the zip file are installation instructions.", user.getDisplayName());\r
+                                                       }\r
+                                                       Multipart mp = new MimeMultipart();\r
+                                                       MimeBodyPart messagePart = new MimeBodyPart();\r
+                                                       messagePart.setText(body);\r
+                                                       mp.addBodyPart(messagePart);\r
+\r
+                                                       // attach zip\r
+                                                       MimeBodyPart filePart = new MimeBodyPart();\r
+                                                       FileDataSource fds = new FileDataSource(zip);\r
+                                                       filePart.setDataHandler(new DataHandler(fds));\r
+                                                       filePart.setFileName(fds.getName());\r
+                                                       mp.addBodyPart(filePart);\r
+\r
+                                                       message.setContent(mp);\r
+\r
+                                                       mail.sendNow(message);\r
+                                               } else {\r
+                                                       JOptionPane.showMessageDialog(GitblitAuthority.this, "Sorry, the mail server settings are not configured properly.\nCan not send email.", Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);\r
+                                               }\r
+                                       } catch (Exception e) {\r
+                                               Utils.showException(GitblitAuthority.this, e);\r
+                                       }\r
+                               }\r
+                       }\r
+                       \r
+                       @Override\r
+                       public void revoke(UserCertificateModel ucm, X509Certificate cert, RevocationReason reason) {\r
+                               File caRevocationList = new File(folder, X509Utils.CA_REVOCATION_LIST);\r
+                               File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE);\r
+                               if (X509Utils.revoke(cert, reason, caRevocationList, caKeystoreFile, caKeystorePassword)) {\r
+                                       File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);\r
+                                       FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());\r
+                                       if (certificatesConfigFile.exists()) {\r
+                                               try {\r
+                                                       config.load();\r
+                                               } catch (Exception e) {\r
+                                                       Utils.showException(GitblitAuthority.this, e);\r
+                                               }\r
+                                       }\r
+                                       // add serial to revoked list\r
+                                       ucm.revoke(cert.getSerialNumber(), reason);\r
+                                       ucm.update(config);\r
+                                       try {\r
+                                               config.save();\r
+                                       } catch (Exception e) {\r
+                                               Utils.showException(GitblitAuthority.this, e);\r
+                                       }\r
+                                       \r
+                                       // refresh user\r
+                                       ucm.certs = null;\r
+                                       int modelIndex = table.convertRowIndexToModel(table.getSelectedRow());\r
+                                       tableModel.fireTableDataChanged();\r
+                                       table.getSelectionModel().setSelectionInterval(modelIndex, modelIndex);\r
+                               }\r
+                       }\r
+               };\r
+               \r
+               table = Utils.newTable(tableModel, Utils.DATE_FORMAT);\r
+               table.setRowSorter(defaultSorter);\r
+               table.setDefaultRenderer(CertificateStatus.class, new CertificateStatusRenderer());\r
+               table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {\r
+\r
+                       @Override\r
+                       public void valueChanged(ListSelectionEvent e) {\r
+                               if (e.getValueIsAdjusting()) {\r
+                                       return;\r
+                               }\r
+                               int row = table.getSelectedRow();\r
+                               if (row < 0) {\r
+                                       return;\r
+                               }\r
+                               int modelIndex = table.convertRowIndexToModel(row);\r
+                               UserCertificateModel ucm = tableModel.get(modelIndex);\r
+                               if (ucm.certs == null) {\r
+                                       ucm.certs = findCerts(folder, ucm.user.username);\r
+                               }\r
+                               userCertificatePanel.setUserCertificateModel(ucm);\r
+                       }\r
+               });\r
+               \r
+               JPanel usersPanel = new JPanel(new BorderLayout()) {\r
+                       \r
+                       private static final long serialVersionUID = 1L;\r
+\r
+                       @Override\r
+                       public Insets getInsets() {\r
+                               return Utils.INSETS;\r
+                       }\r
+               };\r
+               usersPanel.add(new HeaderPanel(Translation.get("gb.users"), "users_16x16.png"), BorderLayout.NORTH);\r
+               usersPanel.add(new JScrollPane(table), BorderLayout.CENTER);\r
+               usersPanel.setMinimumSize(new Dimension(400, 10));\r
+               \r
+               final JTextField filterTextfield = new JTextField(20);\r
+               filterTextfield.addActionListener(new ActionListener() {\r
+                       public void actionPerformed(ActionEvent e) {\r
+                               filterUsers(filterTextfield.getText());\r
+                       }\r
+               });\r
+               filterTextfield.addKeyListener(new KeyAdapter() {\r
+                       public void keyReleased(KeyEvent e) {\r
+                               filterUsers(filterTextfield.getText());\r
+                       }\r
+               });\r
+\r
+               JPanel userControls = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 5));\r
+               userControls.add(new JLabel(Translation.get("gb.filter")));\r
+               userControls.add(filterTextfield);\r
+               \r
+               JPanel leftPanel = new JPanel(new BorderLayout());\r
+               leftPanel.add(userControls, BorderLayout.NORTH);\r
+               leftPanel.add(usersPanel, BorderLayout.CENTER);\r
+               \r
+               userCertificatePanel.setMinimumSize(new Dimension(375, 10));\r
+               \r
+               JPanel root = new JPanel(new BorderLayout()) {\r
+                       private static final long serialVersionUID = 1L;\r
+                       public Insets getInsets() {\r
+                               return Utils.INSETS;\r
+                       }\r
+               };\r
+               JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, userCertificatePanel);\r
+               splitPane.setDividerLocation(1d);\r
+               root.add(splitPane);\r
+               return root;\r
+       }\r
+       \r
+       private void filterUsers(final String fragment) {\r
+               if (StringUtils.isEmpty(fragment)) {\r
+                       table.setRowSorter(defaultSorter);\r
+                       return;\r
+               }\r
+               RowFilter<UserCertificateTableModel, Object> containsFilter = new RowFilter<UserCertificateTableModel, Object>() {\r
+                       public boolean include(Entry<? extends UserCertificateTableModel, ? extends Object> entry) {\r
+                               for (int i = entry.getValueCount() - 1; i >= 0; i--) {\r
+                                       if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) {\r
+                                               return true;\r
+                                       }\r
+                               }\r
+                               return false;\r
+                       }\r
+               };\r
+               TableRowSorter<UserCertificateTableModel> sorter = new TableRowSorter<UserCertificateTableModel>(\r
+                               tableModel);\r
+               sorter.setRowFilter(containsFilter);\r
+               table.setRowSorter(sorter);\r
+       }\r
+}\r
index ad7d58c1f1ba8668c6b1682d3528df78dee29726..68a350660db3e01a701570f12935b824f3b5f265 100644 (file)
  */\r
 package com.gitblit.utils;\r
 \r
+import java.security.cert.CertificateExpiredException;\r
+import java.security.cert.CertificateNotYetValidException;\r
+import java.security.cert.X509Certificate;\r
+import java.text.MessageFormat;\r
+import java.util.Date;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
 import javax.servlet.http.HttpServletRequest;\r
 \r
+import org.slf4j.LoggerFactory;\r
+\r
+import com.gitblit.models.UserModel;\r
+\r
 /**\r
  * Collection of utility methods for http requests.\r
  * \r
@@ -92,4 +104,90 @@ public class HttpUtils {
                sb.append(context);\r
                return sb.toString();\r
        }\r
+       \r
+       /**\r
+        * Returns a user model object built from attributes in the SSL certificate.\r
+        * This model is not retrieved from the user service.\r
+        *  \r
+        * @param httpRequest\r
+        * @param checkValidity ensure certificate can be used now\r
+        * @param usernameOIDs if unspecified, CN is used as the username\r
+        * @return a UserModel, if a valid certificate is in the request, null otherwise\r
+        */\r
+       public static UserModel getUserModelFromCertificate(HttpServletRequest httpRequest, boolean checkValidity, String... usernameOIDs) {\r
+               if (httpRequest.getAttribute("javax.servlet.request.X509Certificate") != null) {\r
+                       X509Certificate[] certChain = (X509Certificate[]) httpRequest\r
+                                       .getAttribute("javax.servlet.request.X509Certificate");\r
+                       if (certChain != null) {\r
+                               X509Certificate cert = certChain[0];\r
+                               // ensure certificate is valid\r
+                               if (checkValidity) {\r
+                                       try {\r
+                                               cert.checkValidity(new Date());\r
+                                       } catch (CertificateNotYetValidException e) {\r
+                                               LoggerFactory.getLogger(HttpUtils.class).info(MessageFormat.format("X509 certificate {0} is not yet valid", cert.getSubjectDN().getName()));\r
+                                               return null;\r
+                                       } catch (CertificateExpiredException e) {\r
+                                               LoggerFactory.getLogger(HttpUtils.class).info(MessageFormat.format("X509 certificate {0} has expired", cert.getSubjectDN().getName()));\r
+                                               return null;\r
+                                       }\r
+                               }\r
+                               return getUserModelFromCertificate(cert, usernameOIDs);\r
+                       }\r
+               }\r
+               return null;\r
+       }\r
+       \r
+       /**\r
+        * Creates a UserModel from a certificate\r
+        * @param cert\r
+        * @param usernameOids if unspecified CN is used as the username\r
+        * @return\r
+        */\r
+       public static UserModel getUserModelFromCertificate(X509Certificate cert, String... usernameOIDs) {\r
+               UserModel user = new UserModel(null);\r
+               user.isAuthenticated = false;\r
+               \r
+               // manually split DN into OID components\r
+               // this is instead of parsing with LdapName which:\r
+               // (1) I don't trust the order of values\r
+               // (2) it filters out values like EMAILADDRESS\r
+               String dn = cert.getSubjectDN().getName();\r
+               Map<String, String> oids = new HashMap<String, String>();\r
+               for (String kvp : dn.split(",")) {\r
+                       String [] val = kvp.trim().split("=");\r
+                       String oid = val[0].toUpperCase().trim();\r
+                       String data = val[1].trim();\r
+                       oids.put(oid, data);\r
+               }\r
+               \r
+               if (usernameOIDs == null || usernameOIDs.length == 0) {\r
+                       // use default usename<->CN mapping\r
+                       usernameOIDs = new String [] { "CN" };\r
+               }\r
+               \r
+               // determine username from OID fingerprint\r
+               StringBuilder an = new StringBuilder();\r
+               for (String oid : usernameOIDs) {\r
+                       String val = getOIDValue(oid.toUpperCase(), oids);\r
+                       if (val != null) {\r
+                               an.append(val).append(' ');\r
+                       }\r
+               }\r
+               user.username = an.toString().trim();\r
+               \r
+               // extract email address, if available\r
+               user.emailAddress = getOIDValue("E", oids);\r
+               if (user.emailAddress == null) {\r
+                       user.emailAddress = getOIDValue("EMAILADDRESS", oids);\r
+               }               \r
+               return user;\r
+       }\r
+       \r
+       private static String getOIDValue(String oid, Map<String, String> oids) {\r
+               if (oids.containsKey(oid)) {\r
+                       return oids.get(oid);\r
+               }\r
+               return null;\r
+       }\r
 }\r
index ceeb91208f8eeeb745a08c18ae3007253c04e9b3..05640ad0d4706c022e00b819fbe4b7637eec3797 100644 (file)
@@ -131,13 +131,16 @@ public abstract class BasePage extends WebPage {
        }       \r
 \r
        private void login() {\r
-               Cookie[] cookies = ((WebRequest) getRequestCycle().getRequest()).getCookies();\r
-               UserModel user = null;\r
-               if (GitBlit.self().allowCookieAuthentication() && cookies != null && cookies.length > 0) {\r
-                       // Grab cookie from Browser Session\r
-                       user = GitBlit.self().authenticate(cookies);\r
-               } else {\r
-                       user = GitBlit.self().authenticate(((WebRequest) getRequestCycle().getRequest()).getHttpServletRequest());\r
+               // try to authenticate by servlet request\r
+               UserModel user = GitBlit.self().authenticate(((WebRequest) getRequestCycle().getRequest()).getHttpServletRequest());\r
+\r
+               if (user == null) {\r
+                       // try to authenticate by cookie\r
+                       Cookie[] cookies = ((WebRequest) getRequestCycle().getRequest()).getCookies();\r
+                       if (GitBlit.self().allowCookieAuthentication() && cookies != null && cookies.length > 0) {\r
+                               // Grab cookie from Browser Session\r
+                               user = GitBlit.self().authenticate(cookies);\r
+                       }\r
                }\r
 \r
                // Login the user\r