summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJames Moger <james.moger@gitblit.com>2012-04-24 14:19:40 -0700
committerJames Moger <james.moger@gitblit.com>2012-04-24 14:19:40 -0700
commit0cb7a9c08cfaebeace058dc806099051f163f172 (patch)
tree78caa4a7fec75adbec367a1026b36d9e8ebee2e4 /src
parenta9eb6b1105cd47f00ae45dacda9af8e829ade191 (diff)
parent3d699cb14d6c891338567c562f7fe8d81e318b1c (diff)
downloadgitblit-0cb7a9c08cfaebeace058dc806099051f163f172.tar.gz
gitblit-0cb7a9c08cfaebeace058dc806099051f163f172.zip
Merge pull request #12 from jcrygier/ldap_unboundid
Ldap unboundid
Diffstat (limited to 'src')
-rw-r--r--src/com/gitblit/ConfigUserService.java25
-rw-r--r--src/com/gitblit/FileUserService.java27
-rw-r--r--src/com/gitblit/GitBlit.java16
-rw-r--r--src/com/gitblit/GitBlitServer.java42
-rw-r--r--src/com/gitblit/GitblitUserService.java47
-rw-r--r--src/com/gitblit/IStoredSettings.java18
-rw-r--r--src/com/gitblit/IUserService.java16
-rw-r--r--src/com/gitblit/LdapUserService.java268
-rw-r--r--src/com/gitblit/build/Build.java6
-rw-r--r--src/com/gitblit/utils/ConnectionUtils.java89
-rw-r--r--src/com/gitblit/utils/StringUtils.java60
-rw-r--r--src/com/gitblit/wicket/GitBlitWebApp.properties4
-rw-r--r--src/com/gitblit/wicket/pages/BasePage.java6
-rw-r--r--src/com/gitblit/wicket/pages/ChangePasswordPage.java6
-rw-r--r--src/com/gitblit/wicket/pages/EditTeamPage.java5
-rw-r--r--src/com/gitblit/wicket/pages/EditUserPage.java82
-rw-r--r--src/com/gitblit/wicket/panels/UsersPanel.java3
17 files changed, 662 insertions, 58 deletions
diff --git a/src/com/gitblit/ConfigUserService.java b/src/com/gitblit/ConfigUserService.java
index 8f47f7a0..828ba762 100644
--- a/src/com/gitblit/ConfigUserService.java
+++ b/src/com/gitblit/ConfigUserService.java
@@ -100,6 +100,27 @@ public class ConfigUserService implements IUserService {
}
/**
+ * Does the user service support changes to credentials?
+ *
+ * @return true or false
+ * @since 1.0.0
+ */
+ @Override
+ public boolean supportsCredentialChanges() {
+ return true;
+ }
+
+ /**
+ * Does the user service support changes to team memberships?
+ *
+ * @return true or false
+ * @since 1.0.0
+ */
+ public boolean supportsTeamMembershipChanges() {
+ return true;
+ }
+
+ /**
* Does the user service support cookie authentication?
*
* @return true or false
@@ -656,7 +677,9 @@ public class ConfigUserService implements IUserService {
// write users
for (UserModel model : users.values()) {
- config.setString(USER, model.username, PASSWORD, model.password);
+ if (!StringUtils.isEmpty(model.password)) {
+ config.setString(USER, model.username, PASSWORD, model.password);
+ }
// user roles
List<String> roles = new ArrayList<String>();
diff --git a/src/com/gitblit/FileUserService.java b/src/com/gitblit/FileUserService.java
index 7842c31d..b8d4a40e 100644
--- a/src/com/gitblit/FileUserService.java
+++ b/src/com/gitblit/FileUserService.java
@@ -74,6 +74,27 @@ public class FileUserService extends FileSettings implements IUserService {
}
/**
+ * Does the user service support changes to credentials?
+ *
+ * @return true or false
+ * @since 1.0.0
+ */
+ @Override
+ public boolean supportsCredentialChanges() {
+ return true;
+ }
+
+ /**
+ * Does the user service support changes to team memberships?
+ *
+ * @return true or false
+ * @since 1.0.0
+ */
+ public boolean supportsTeamMembershipChanges() {
+ return true;
+ }
+
+ /**
* Does the user service support cookie authentication?
*
* @return true or false
@@ -233,7 +254,9 @@ public class FileUserService extends FileSettings implements IUserService {
}
StringBuilder sb = new StringBuilder();
- sb.append(model.password);
+ if (!StringUtils.isEmpty(model.password)) {
+ sb.append(model.password);
+ }
sb.append(',');
for (String role : roles) {
sb.append(role);
@@ -658,6 +681,8 @@ public class FileUserService extends FileSettings implements IUserService {
team.addRepositories(repositories);
team.addUsers(users);
team.addMailingLists(mailingLists);
+ team.preReceiveScripts.addAll(preReceive);
+ team.postReceiveScripts.addAll(postReceive);
teams.put(team.name.toLowerCase(), team);
} else {
// user definition
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index bfe93d09..2b7ba3c1 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -377,6 +377,22 @@ public class GitBlit implements ServletContextListener {
this.userService = userService;
this.userService.setup(settings);
}
+
+ /**
+ *
+ * @return true if the user service supports credential changes
+ */
+ public boolean supportsCredentialChanges() {
+ return userService.supportsCredentialChanges();
+ }
+
+ /**
+ *
+ * @return true if the user service supports team membership changes
+ */
+ public boolean supportsTeamMembershipChanges() {
+ return userService.supportsTeamMembershipChanges();
+ }
/**
* Authenticate a user based on a username and password.
diff --git a/src/com/gitblit/GitBlitServer.java b/src/com/gitblit/GitBlitServer.java
index ce0d1fbb..1307bc3d 100644
--- a/src/com/gitblit/GitBlitServer.java
+++ b/src/com/gitblit/GitBlitServer.java
@@ -23,12 +23,14 @@ import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
+import java.net.URI;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.ProtectionDomain;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
+import java.util.Scanner;
import org.eclipse.jetty.ajp.Ajp13SocketConnector;
import org.eclipse.jetty.server.Connector;
@@ -50,6 +52,10 @@ import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameters;
import com.gitblit.utils.StringUtils;
+import com.unboundid.ldap.listener.InMemoryDirectoryServer;
+import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
+import com.unboundid.ldap.listener.InMemoryListenerConfig;
+import com.unboundid.ldif.LDIFReader;
/**
* GitBlitServer is the embedded Jetty server for Gitblit GO. This class starts
@@ -268,6 +274,39 @@ public class GitBlitServer {
// Override settings from the command-line
settings.overrideSetting(Keys.realm.userService, params.userService);
settings.overrideSetting(Keys.git.repositoriesFolder, params.repositoriesFolder);
+
+ // Start up an in-memory LDAP server, if configured
+ try {
+ if (StringUtils.isEmpty(params.ldapLdifFile) == false) {
+ File ldifFile = new File(params.ldapLdifFile);
+ if (ldifFile != null && ldifFile.exists()) {
+ URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap_server));
+ String firstLine = new Scanner(ldifFile).nextLine();
+ String rootDN = firstLine.substring(4);
+ String bindUserName = settings.getString(Keys.realm.ldap_username, "");
+ String bindPassword = settings.getString(Keys.realm.ldap_password, "");
+
+ // Get the port
+ int port = ldapUrl.getPort();
+ if (port == -1)
+ port = 389;
+
+ InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(rootDN);
+ config.addAdditionalBindCredentials(bindUserName, bindPassword);
+ config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", port));
+ config.setSchema(null);
+
+ InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
+ ds.importFromLDIF(true, new LDIFReader(ldifFile));
+ ds.startListening();
+
+ logger.info("LDAP Server started at ldap://localhost:" + port);
+ }
+ }
+ } catch (Exception e) {
+ // Completely optional, just show a warning
+ logger.warn("Unable to start LDAP server", e);
+ }
// Set the server's contexts
server.setHandler(rootContext);
@@ -506,6 +545,9 @@ public class GitBlitServer {
*/
@Parameter(names = { "--settings" }, description = "Path to alternative settings")
public String settingsfile;
+
+ @Parameter(names = { "--ldapLdifFile" }, description = "Path to LDIF file. This will cause an in-memory LDAP server to be started according to gitblit settings")
+ public String ldapLdifFile;
}
} \ No newline at end of file
diff --git a/src/com/gitblit/GitblitUserService.java b/src/com/gitblit/GitblitUserService.java
index 7462af08..ddb3ca77 100644
--- a/src/com/gitblit/GitblitUserService.java
+++ b/src/com/gitblit/GitblitUserService.java
@@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
+import com.gitblit.utils.DeepCopier;
/**
* This class wraps the default user service and is recommended as the starting
@@ -112,6 +113,16 @@ public class GitblitUserService implements IUserService {
}
@Override
+ public boolean supportsCredentialChanges() {
+ return serviceImpl.supportsCredentialChanges();
+ }
+
+ @Override
+ public boolean supportsTeamMembershipChanges() {
+ return serviceImpl.supportsTeamMembershipChanges();
+ }
+
+ @Override
public boolean supportsCookies() {
return serviceImpl.supportsCookies();
}
@@ -143,9 +154,33 @@ public class GitblitUserService implements IUserService {
@Override
public boolean updateUserModel(String username, UserModel model) {
- return serviceImpl.updateUserModel(username, model);
+ if (supportsCredentialChanges()) {
+ if (!supportsTeamMembershipChanges()) {
+ // teams are externally controlled - copy from original model
+ UserModel existingModel = getUserModel(username);
+
+ model = DeepCopier.copy(model);
+ model.teams.clear();
+ model.teams.addAll(existingModel.teams);
+ }
+ return serviceImpl.updateUserModel(username, model);
+ }
+ if (model.username.equals(username)) {
+ // passwords are not persisted by the backing user service
+ model.password = null;
+ if (!supportsTeamMembershipChanges()) {
+ // teams are externally controlled- copy from original model
+ UserModel existingModel = getUserModel(username);
+
+ model = DeepCopier.copy(model);
+ model.teams.clear();
+ model.teams.addAll(existingModel.teams);
+ }
+ return serviceImpl.updateUserModel(username, model);
+ }
+ logger.error("Users can not be renamed!");
+ return false;
}
-
@Override
public boolean deleteUserModel(UserModel model) {
return serviceImpl.deleteUserModel(model);
@@ -198,6 +233,14 @@ public class GitblitUserService implements IUserService {
@Override
public boolean updateTeamModel(String teamname, TeamModel model) {
+ if (!supportsTeamMembershipChanges()) {
+ // teams are externally controlled - copy from original model
+ TeamModel existingModel = getTeamModel(teamname);
+
+ model = DeepCopier.copy(model);
+ model.users.clear();
+ model.users.addAll(existingModel.users);
+ }
return serviceImpl.updateTeamModel(teamname, model);
}
diff --git a/src/com/gitblit/IStoredSettings.java b/src/com/gitblit/IStoredSettings.java
index 2d8b6055..2f45f09d 100644
--- a/src/com/gitblit/IStoredSettings.java
+++ b/src/com/gitblit/IStoredSettings.java
@@ -157,6 +157,24 @@ public abstract class IStoredSettings {
}
return defaultValue;
}
+
+ /**
+ * Returns the string value for the specified key. If the key does not
+ * exist an exception is thrown.
+ *
+ * @param key
+ * @return key value
+ */
+ public String getRequiredString(String name) {
+ Properties props = getSettings();
+ if (props.containsKey(name)) {
+ String value = props.getProperty(name);
+ if (value != null) {
+ return value.trim();
+ }
+ }
+ throw new RuntimeException("Property (" + name + ") does not exist");
+ }
/**
* Returns a list of space-separated strings from the specified key.
diff --git a/src/com/gitblit/IUserService.java b/src/com/gitblit/IUserService.java
index a5e04e3e..334bbedd 100644
--- a/src/com/gitblit/IUserService.java
+++ b/src/com/gitblit/IUserService.java
@@ -40,6 +40,22 @@ public interface IUserService {
void setup(IStoredSettings settings);
/**
+ * Does the user service support changes to credentials?
+ *
+ * @return true or false
+ * @since 1.0.0
+ */
+ boolean supportsCredentialChanges();
+
+ /**
+ * Does the user service support changes to team memberships?
+ *
+ * @return true or false
+ * @since 1.0.0
+ */
+ boolean supportsTeamMembershipChanges();
+
+ /**
* Does the user service support cookie authentication?
*
* @return true or false
diff --git a/src/com/gitblit/LdapUserService.java b/src/com/gitblit/LdapUserService.java
new file mode 100644
index 00000000..86b61364
--- /dev/null
+++ b/src/com/gitblit/LdapUserService.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2012 John Crygier
+ * 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;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.GeneralSecurityException;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
+import com.unboundid.ldap.sdk.Attribute;
+import com.unboundid.ldap.sdk.LDAPConnection;
+import com.unboundid.ldap.sdk.LDAPException;
+import com.unboundid.ldap.sdk.LDAPSearchException;
+import com.unboundid.ldap.sdk.SearchResult;
+import com.unboundid.ldap.sdk.SearchResultEntry;
+import com.unboundid.ldap.sdk.SearchScope;
+import com.unboundid.util.ssl.SSLUtil;
+import com.unboundid.util.ssl.TrustAllTrustManager;
+
+/**
+ * Implementation of an LDAP user service.
+ *
+ * @author John Crygier
+ */
+public class LdapUserService extends GitblitUserService {
+
+ public static final Logger logger = LoggerFactory.getLogger(LdapUserService.class);
+
+ private IStoredSettings settings;
+
+ public LdapUserService() {
+ super();
+ }
+
+ @Override
+ public void setup(IStoredSettings settings) {
+ this.settings = settings;
+ String file = settings.getString(Keys.realm.ldap_backingUserService, "users.conf");
+ File realmFile = GitBlit.getFileOrFolder(file);
+
+ serviceImpl = createUserService(realmFile);
+ logger.info("LDAP User Service backed by " + serviceImpl.toString());
+ }
+
+ private LDAPConnection getLdapConnection() {
+ try {
+ URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap_server));
+ String bindUserName = settings.getString(Keys.realm.ldap_username, "");
+ String bindPassword = settings.getString(Keys.realm.ldap_password, "");
+ int ldapPort = ldapUrl.getPort();
+
+ if (ldapUrl.getScheme().equalsIgnoreCase("ldaps")) { // SSL
+ if (ldapPort == -1) // Default Port
+ ldapPort = 636;
+
+ SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
+ return new LDAPConnection(sslUtil.createSSLSocketFactory(), ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
+ } else {
+ if (ldapPort == -1) // Default Port
+ ldapPort = 389;
+
+ return new LDAPConnection(ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
+ }
+ } catch (URISyntaxException e) {
+ logger.error("Bad LDAP URL, should be in the form: ldap(s)://<server>:<port>", e);
+ } catch (GeneralSecurityException e) {
+ logger.error("Unable to create SSL Connection", e);
+ } catch (LDAPException e) {
+ logger.error("Error Connecting to LDAP", e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Credentials are defined in the LDAP server and can not be manipulated
+ * from Gitblit.
+ *
+ * @return false
+ * @since 1.0.0
+ */
+ @Override
+ public boolean supportsCredentialChanges() {
+ return false;
+ }
+
+ /**
+ * If the LDAP server will maintain team memberships then LdapUserService
+ * will not allow team membership changes. In this scenario all team
+ * changes must be made on the LDAP server by the LDAP administrator.
+ *
+ * @return true or false
+ * @since 1.0.0
+ */
+ public boolean supportsTeamMembershipChanges() {
+ return !settings.getBoolean(Keys.realm.ldap_maintainTeams, false);
+ }
+
+ /**
+ * Does the user service support cookie authentication?
+ *
+ * @return true or false
+ */
+ @Override
+ public boolean supportsCookies() {
+ // TODO cookies need to be reviewed
+ return false;
+ }
+
+ @Override
+ public UserModel authenticate(String username, char[] password) {
+ String simpleUsername = getSimpleUsername(username);
+
+ LDAPConnection ldapConnection = getLdapConnection();
+ if (ldapConnection != null) {
+ // Find the logging in user's DN
+ String accountBase = settings.getString(Keys.realm.ldap_accountBase, "");
+ String accountPattern = settings.getString(Keys.realm.ldap_accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
+ accountPattern = StringUtils.replace(accountPattern, "${username}", simpleUsername);
+
+ SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
+ if (result != null && result.getEntryCount() == 1) {
+ SearchResultEntry loggingInUser = result.getSearchEntries().get(0);
+ String loggingInUserDN = loggingInUser.getDN();
+
+ if (isAuthenticated(ldapConnection, loggingInUserDN, new String(password))) {
+ logger.debug("Authenitcated: " + username);
+
+ UserModel user = getUserModel(simpleUsername);
+ if (user == null) // create user object for new authenticated user
+ user = createUserFromLdap(simpleUsername, loggingInUser);
+
+ user.password = "StoredInLDAP";
+
+ if (!supportsTeamMembershipChanges())
+ getTeamsFromLdap(ldapConnection, simpleUsername, loggingInUser, user);
+
+ // Get Admin Attributes
+ setAdminAttribute(user);
+
+ // Push the ldap looked up values to backing file
+ super.updateUserModel(user);
+ if (!supportsTeamMembershipChanges()) {
+ for (TeamModel userTeam : user.teams)
+ updateTeamModel(userTeam);
+ }
+
+ return user;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void setAdminAttribute(UserModel user) {
+ user.canAdmin = false;
+ List<String> admins = settings.getStrings(Keys.realm.ldap_admins);
+ for (String admin : admins) {
+ if (admin.startsWith("@")) { // Team
+ if (user.getTeam(admin.substring(1)) != null)
+ user.canAdmin = true;
+ } else
+ if (user.getName().equalsIgnoreCase(admin))
+ user.canAdmin = true;
+ }
+ }
+
+ private void getTeamsFromLdap(LDAPConnection ldapConnection, String simpleUsername, SearchResultEntry loggingInUser, UserModel user) {
+ String loggingInUserDN = loggingInUser.getDN();
+
+ user.teams.clear(); // Clear the users team memberships - we're going to get them from LDAP
+ String groupBase = settings.getString(Keys.realm.ldap_groupBase, "");
+ String groupMemberPattern = settings.getString(Keys.realm.ldap_groupMemberPattern, "(&(objectClass=group)(member=${dn}))");
+
+ groupMemberPattern = StringUtils.replace(groupMemberPattern, "${dn}", loggingInUserDN);
+ groupMemberPattern = StringUtils.replace(groupMemberPattern, "${username}", simpleUsername);
+
+ // Fill in attributes into groupMemberPattern
+ for (Attribute userAttribute : loggingInUser.getAttributes())
+ groupMemberPattern = StringUtils.replace(groupMemberPattern, "${" + userAttribute.getName() + "}", userAttribute.getValue());
+
+ SearchResult teamMembershipResult = doSearch(ldapConnection, groupBase, groupMemberPattern);
+ if (teamMembershipResult != null && teamMembershipResult.getEntryCount() > 0) {
+ for (int i = 0; i < teamMembershipResult.getEntryCount(); i++) {
+ SearchResultEntry teamEntry = teamMembershipResult.getSearchEntries().get(i);
+ String teamName = teamEntry.getAttribute("cn").getValue();
+
+ TeamModel teamModel = getTeamModel(teamName);
+ if (teamModel == null)
+ teamModel = createTeamFromLdap(teamEntry);
+
+ user.teams.add(teamModel);
+ teamModel.addUser(user.getName());
+ }
+ }
+ }
+
+ private TeamModel createTeamFromLdap(SearchResultEntry teamEntry) {
+ TeamModel answer = new TeamModel(teamEntry.getAttributeValue("cn"));
+ // If attributes other than team name ever from from LDAP, this is where to get them
+
+ return answer;
+ }
+
+ private UserModel createUserFromLdap(String simpleUserName, SearchResultEntry userEntry) {
+ UserModel answer = new UserModel(simpleUserName);
+ //If attributes other than user name ever from from LDAP, this is where to get them
+
+ return answer;
+ }
+
+ private SearchResult doSearch(LDAPConnection ldapConnection, String base, String filter) {
+ try {
+ return ldapConnection.search(base, SearchScope.SUB, filter);
+ } catch (LDAPSearchException e) {
+ logger.error("Problem Searching LDAP", e);
+
+ return null;
+ }
+ }
+
+ private boolean isAuthenticated(LDAPConnection ldapConnection, String userDn, String password) {
+ try {
+ ldapConnection.bind(userDn, password);
+ return true;
+ } catch (LDAPException e) {
+ logger.error("Error authenitcating user", e);
+ return false;
+ }
+ }
+
+
+ /**
+ * Returns a simple username without any domain prefixes.
+ *
+ * @param username
+ * @return a simple username
+ */
+ protected String getSimpleUsername(String username) {
+ int lastSlash = username.lastIndexOf('\\');
+ if (lastSlash > -1) {
+ username = username.substring(lastSlash + 1);
+ }
+ return username;
+ }
+}
diff --git a/src/com/gitblit/build/Build.java b/src/com/gitblit/build/Build.java
index 96f848b4..cbe09a9c 100644
--- a/src/com/gitblit/build/Build.java
+++ b/src/com/gitblit/build/Build.java
@@ -94,6 +94,7 @@ public class Build {
downloadFromApache(MavenObject.LUCENE, BuildType.RUNTIME);
downloadFromApache(MavenObject.LUCENE_HIGHLIGHTER, BuildType.RUNTIME);
downloadFromApache(MavenObject.LUCENE_MEMORY, BuildType.RUNTIME);
+ downloadFromApache(MavenObject.UNBOUND_ID, BuildType.RUNTIME);
downloadFromEclipse(MavenObject.JGIT, BuildType.RUNTIME);
downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.RUNTIME);
@@ -124,6 +125,7 @@ public class Build {
downloadFromApache(MavenObject.LUCENE, BuildType.COMPILETIME);
downloadFromApache(MavenObject.LUCENE_HIGHLIGHTER, BuildType.COMPILETIME);
downloadFromApache(MavenObject.LUCENE_MEMORY, BuildType.COMPILETIME);
+ downloadFromApache(MavenObject.UNBOUND_ID, BuildType.COMPILETIME);
downloadFromEclipse(MavenObject.JGIT, BuildType.COMPILETIME);
downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.COMPILETIME);
@@ -524,6 +526,10 @@ public class Build {
public static final MavenObject LUCENE_MEMORY = new MavenObject("lucene memory", "org/apache/lucene", "lucene-memory",
"3.5.0", 30000, 23000, 0, "7908e954e8c1b4b2463aa712b34fa4a5612e241d",
"69b19b38d78cc3b27ea5542a14f0ebbb1625ffdd", "");
+
+ public static final MavenObject UNBOUND_ID = new MavenObject("unbound id", "com/unboundid", "unboundid-ldapsdk",
+ "2.3.0", 1383417, 1439721, 0, "6fde8d9fb4ee3e7e3d7e764e3ea57195971e2eb2",
+ "5276d3d29630693dba99ab9f7ea54f4c471d3af1", "");
public final String name;
diff --git a/src/com/gitblit/utils/ConnectionUtils.java b/src/com/gitblit/utils/ConnectionUtils.java
index 9ad62d0e..f0b41118 100644
--- a/src/com/gitblit/utils/ConnectionUtils.java
+++ b/src/com/gitblit/utils/ConnectionUtils.java
@@ -16,16 +16,22 @@
package com.gitblit.utils;
import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
+import java.net.UnknownHostException;
+import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import javax.net.SocketFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
@@ -87,6 +93,89 @@ public class ConnectionUtils {
}
return conn;
}
+
+ // Copyright (C) 2009 The Android Open Source Project
+ //
+ // 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.
+ public static class BlindSSLSocketFactory extends SSLSocketFactory {
+ private static final BlindSSLSocketFactory INSTANCE;
+
+ static {
+ try {
+ final SSLContext context = SSLContext.getInstance("SSL");
+ final TrustManager[] trustManagers = { new DummyTrustManager() };
+ final SecureRandom rng = new SecureRandom();
+ context.init(null, trustManagers, rng);
+ INSTANCE = new BlindSSLSocketFactory(context.getSocketFactory());
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException("Cannot create BlindSslSocketFactory", e);
+ }
+ }
+
+ public static SocketFactory getDefault() {
+ return INSTANCE;
+ }
+
+ private final SSLSocketFactory sslFactory;
+
+ private BlindSSLSocketFactory(final SSLSocketFactory sslFactory) {
+ this.sslFactory = sslFactory;
+ }
+
+ @Override
+ public Socket createSocket(Socket s, String host, int port, boolean autoClose)
+ throws IOException {
+ return sslFactory.createSocket(s, host, port, autoClose);
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return sslFactory.getDefaultCipherSuites();
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return sslFactory.getSupportedCipherSuites();
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ return sslFactory.createSocket();
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException,
+ UnknownHostException {
+ return sslFactory.createSocket(host, port);
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ return sslFactory.createSocket(host, port);
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost,
+ int localPort) throws IOException, UnknownHostException {
+ return sslFactory.createSocket(host, port, localHost, localPort);
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port,
+ InetAddress localAddress, int localPort) throws IOException {
+ return sslFactory.createSocket(address, port, localAddress, localPort);
+ }
+ }
/**
* DummyTrustManager trusts all certificates.
diff --git a/src/com/gitblit/utils/StringUtils.java b/src/com/gitblit/utils/StringUtils.java
index d6441829..2c357241 100644
--- a/src/com/gitblit/utils/StringUtils.java
+++ b/src/com/gitblit/utils/StringUtils.java
@@ -327,20 +327,24 @@ public class StringUtils {
* @return list of strings
*/
public static List<String> getStringsFromValue(String value, String separator) {
- List<String> strings = new ArrayList<String>();
- try {
- String[] chunks = value.split(separator);
- for (String chunk : chunks) {
- chunk = chunk.trim();
- if (chunk.length() > 0) {
- strings.add(chunk);
- }
- }
- } catch (PatternSyntaxException e) {
- throw new RuntimeException(e);
- }
- return strings;
- }
+ List<String> strings = new ArrayList<String>();
+ try {
+ String[] chunks = value.split(separator + "(?=([^\"]*\"[^\"]*\")*[^\"]*$)");
+ for (String chunk : chunks) {
+ chunk = chunk.trim();
+ if (chunk.length() > 0) {
+ if (chunk.charAt(0) == '"' && chunk.charAt(chunk.length() - 1) == '"') {
+ // strip double quotes
+ chunk = chunk.substring(1, chunk.length() - 1).trim();
+ }
+ strings.add(chunk);
+ }
+ }
+ } catch (PatternSyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ return strings;
+ }
/**
* Validates that a name is composed of letters, digits, or limited other
@@ -518,4 +522,32 @@ public class StringUtils {
}
return "";
}
+
+ /**
+ * Replace all occurences of a substring within a string with
+ * another string.
+ *
+ * From Spring StringUtils.
+ *
+ * @param inString String to examine
+ * @param oldPattern String to replace
+ * @param newPattern String to insert
+ * @return a String with the replacements
+ */
+ public static String replace(String inString, String oldPattern, String newPattern) {
+ StringBuilder sb = new StringBuilder();
+ int pos = 0; // our position in the old string
+ int index = inString.indexOf(oldPattern);
+ // the index of an occurrence we've found, or -1
+ int patLen = oldPattern.length();
+ while (index >= 0) {
+ sb.append(inString.substring(pos, index));
+ sb.append(newPattern);
+ pos = index + patLen;
+ index = inString.indexOf(oldPattern, pos);
+ }
+ sb.append(inString.substring(pos));
+ // remember to append any characters to the right of a match
+ return sb.toString();
+ }
} \ No newline at end of file
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index e73addc8..295db8a3 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -270,4 +270,6 @@ gb.noProposals = Sorry, {0} is not accepting proposals at this time.
gb.noFederation = Sorry, {0} is not configured to federate with any Gitblit instances.
gb.proposalFailed = Sorry, {0} did not receive any proposal data!
gb.proposalError = Sorry, {0} reports that an unexpected error occurred!
-gb.failedToSendProposal = Failed to send proposal! \ No newline at end of file
+gb.failedToSendProposal = Failed to send proposal!
+gb.userServiceDoesNotPermitAddUser = {0} does not permit adding a user account!
+gb.userServiceDoesNotPermitPasswordChanges = {0} does not permit password changes! \ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/BasePage.java b/src/com/gitblit/wicket/pages/BasePage.java
index 3852818a..94ed6334 100644
--- a/src/com/gitblit/wicket/pages/BasePage.java
+++ b/src/com/gitblit/wicket/pages/BasePage.java
@@ -254,9 +254,11 @@ public abstract class BasePage extends WebPage {
add(new Label("username", GitBlitWebSession.get().getUser().toString() + ":"));
add(new LinkPanel("loginLink", null, markupProvider.getString("gb.logout"),
LogoutPage.class));
+ boolean editCredentials = GitBlit.self().supportsCredentialChanges();
// quick and dirty hack for showing a separator
- add(new Label("separator", "|"));
- add(new BookmarkablePageLink<Void>("changePasswordLink", ChangePasswordPage.class));
+ add(new Label("separator", "|").setVisible(editCredentials));
+ add(new BookmarkablePageLink<Void>("changePasswordLink",
+ ChangePasswordPage.class).setVisible(editCredentials));
} else {
// login
add(new Label("username").setVisible(false));
diff --git a/src/com/gitblit/wicket/pages/ChangePasswordPage.java b/src/com/gitblit/wicket/pages/ChangePasswordPage.java
index 4fb5d237..cbe732ff 100644
--- a/src/com/gitblit/wicket/pages/ChangePasswordPage.java
+++ b/src/com/gitblit/wicket/pages/ChangePasswordPage.java
@@ -50,6 +50,12 @@ public class ChangePasswordPage extends RootSubPage {
// no authentication enabled
throw new RestartResponseException(getApplication().getHomePage());
}
+
+ if (!GitBlit.self().supportsCredentialChanges()) {
+ error(MessageFormat.format(getString("gb.userServiceDoesNotPermitPasswordChanges"),
+ GitBlit.getString(Keys.realm.userService, "users.conf")), true);
+ }
+
setupPage(getString("gb.changePassword"), GitBlitWebSession.get().getUser().username);
StatelessForm<Void> form = new StatelessForm<Void>("passwordForm") {
diff --git a/src/com/gitblit/wicket/pages/EditTeamPage.java b/src/com/gitblit/wicket/pages/EditTeamPage.java
index 890ea8f7..96bd188f 100644
--- a/src/com/gitblit/wicket/pages/EditTeamPage.java
+++ b/src/com/gitblit/wicket/pages/EditTeamPage.java
@@ -217,9 +217,12 @@ public class EditTeamPage extends RootSubPage {
// do not let the browser pre-populate these fields
form.add(new SimpleAttributeModifier("autocomplete", "off"));
+ // not all user services support manipulating team memberships
+ boolean editMemberships = GitBlit.self().supportsTeamMembershipChanges();
+
// field names reflective match TeamModel fields
form.add(new TextField<String>("name"));
- form.add(users);
+ form.add(users.setEnabled(editMemberships));
mailingLists = new Model<String>(teamModel.mailingLists == null ? ""
: StringUtils.flattenStrings(teamModel.mailingLists, " "));
form.add(new TextField<String>("mailingLists", mailingLists));
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.java b/src/com/gitblit/wicket/pages/EditUserPage.java
index 36f7578d..103d672a 100644
--- a/src/com/gitblit/wicket/pages/EditUserPage.java
+++ b/src/com/gitblit/wicket/pages/EditUserPage.java
@@ -54,6 +54,10 @@ public class EditUserPage extends RootSubPage {
public EditUserPage() {
// create constructor
super();
+ if (!GitBlit.self().supportsCredentialChanges()) {
+ error(MessageFormat.format(getString("gb.userServiceDoesNotPermitAddUser"),
+ GitBlit.getString(Keys.realm.userService, "users.conf")), true);
+ }
isCreate = true;
setupPage(new UserModel(""));
}
@@ -125,40 +129,42 @@ public class EditUserPage extends RootSubPage {
}
boolean rename = !StringUtils.isEmpty(oldName)
&& !oldName.equalsIgnoreCase(username);
- if (!userModel.password.equals(confirmPassword.getObject())) {
- error(getString("gb.passwordsDoNotMatch"));
- return;
- }
- String password = userModel.password;
- if (!password.toUpperCase().startsWith(StringUtils.MD5_TYPE)
- && !password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) {
- // This is a plain text password.
- // Check length.
- int minLength = GitBlit.getInteger(Keys.realm.minPasswordLength, 5);
- if (minLength < 4) {
- minLength = 4;
- }
- if (password.trim().length() < minLength) {
- error(MessageFormat.format(getString("gb.passwordTooShort"),
- minLength));
+ if (GitBlit.self().supportsCredentialChanges()) {
+ if (!userModel.password.equals(confirmPassword.getObject())) {
+ error(getString("gb.passwordsDoNotMatch"));
return;
}
-
- // Optionally store the password MD5 digest.
- String type = GitBlit.getString(Keys.realm.passwordStorage, "md5");
- if (type.equalsIgnoreCase("md5")) {
- // store MD5 digest of password
- userModel.password = StringUtils.MD5_TYPE
- + StringUtils.getMD5(userModel.password);
- } else if (type.equalsIgnoreCase("combined-md5")) {
- // store MD5 digest of username+password
- userModel.password = StringUtils.COMBINED_MD5_TYPE
- + StringUtils.getMD5(username + userModel.password);
+ String password = userModel.password;
+ if (!password.toUpperCase().startsWith(StringUtils.MD5_TYPE)
+ && !password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) {
+ // This is a plain text password.
+ // Check length.
+ int minLength = GitBlit.getInteger(Keys.realm.minPasswordLength, 5);
+ if (minLength < 4) {
+ minLength = 4;
+ }
+ if (password.trim().length() < minLength) {
+ error(MessageFormat.format(getString("gb.passwordTooShort"),
+ minLength));
+ return;
+ }
+
+ // Optionally store the password MD5 digest.
+ String type = GitBlit.getString(Keys.realm.passwordStorage, "md5");
+ if (type.equalsIgnoreCase("md5")) {
+ // store MD5 digest of password
+ userModel.password = StringUtils.MD5_TYPE
+ + StringUtils.getMD5(userModel.password);
+ } else if (type.equalsIgnoreCase("combined-md5")) {
+ // store MD5 digest of username+password
+ userModel.password = StringUtils.COMBINED_MD5_TYPE
+ + StringUtils.getMD5(username + userModel.password);
+ }
+ } else if (rename
+ && password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) {
+ error(getString("gb.combinedMd5Rename"));
+ return;
}
- } else if (rename
- && password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) {
- error(getString("gb.combinedMd5Rename"));
- return;
}
Iterator<String> selectedRepositories = repositories.getSelectedChoices();
@@ -200,20 +206,26 @@ public class EditUserPage extends RootSubPage {
// do not let the browser pre-populate these fields
form.add(new SimpleAttributeModifier("autocomplete", "off"));
+
+ // not all user services support manipulating username and password
+ boolean editCredentials = GitBlit.self().supportsCredentialChanges();
+
+ // not all user services support manipulating team memberships
+ boolean editTeams = GitBlit.self().supportsTeamMembershipChanges();
// field names reflective match UserModel fields
- form.add(new TextField<String>("username"));
+ form.add(new TextField<String>("username").setEnabled(editCredentials));
PasswordTextField passwordField = new PasswordTextField("password");
passwordField.setResetPassword(false);
- form.add(passwordField);
+ form.add(passwordField.setEnabled(editCredentials));
PasswordTextField confirmPasswordField = new PasswordTextField("confirmPassword",
confirmPassword);
confirmPasswordField.setResetPassword(false);
- form.add(confirmPasswordField);
+ form.add(confirmPasswordField.setEnabled(editCredentials));
form.add(new CheckBox("canAdmin"));
form.add(new CheckBox("excludeFromFederation"));
form.add(repositories);
- form.add(teams);
+ form.add(teams.setEnabled(editTeams));
form.add(new Button("save"));
Button cancel = new Button("cancel") {
diff --git a/src/com/gitblit/wicket/panels/UsersPanel.java b/src/com/gitblit/wicket/panels/UsersPanel.java
index ad2ed922..4b0edb34 100644
--- a/src/com/gitblit/wicket/panels/UsersPanel.java
+++ b/src/com/gitblit/wicket/panels/UsersPanel.java
@@ -39,7 +39,8 @@ public class UsersPanel extends BasePanel {
super(wicketId);
Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
- adminLinks.add(new BookmarkablePageLink<Void>("newUser", EditUserPage.class));
+ adminLinks.add(new BookmarkablePageLink<Void>("newUser", EditUserPage.class)
+ .setVisible(GitBlit.self().supportsCredentialChanges()));
add(adminLinks.setVisible(showAdmin));
final List<UserModel> users = GitBlit.self().getAllUsers();