From e41e8f8c3bc9f5edab1d271464364f95620ece8c Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Thu, 19 Nov 2015 17:55:38 -0500
Subject: [PATCH] Create filestore directory on startup

---
 src/main/java/com/gitblit/auth/LdapAuthProvider.java |  302 +++++++++++++++++++++++++++++++++++---------------
 1 files changed, 211 insertions(+), 91 deletions(-)

diff --git a/src/main/java/com/gitblit/auth/LdapAuthProvider.java b/src/main/java/com/gitblit/auth/LdapAuthProvider.java
index 7a6b74d..cc772e7 100644
--- a/src/main/java/com/gitblit/auth/LdapAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/LdapAuthProvider.java
@@ -19,19 +19,23 @@
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
+import java.text.MessageFormat;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
 
 import com.gitblit.Constants;
 import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
 import com.gitblit.Keys;
 import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
 import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
+import com.gitblit.service.LdapSyncService;
 import com.gitblit.utils.ArrayUtils;
 import com.gitblit.utils.StringUtils;
 import com.unboundid.ldap.sdk.Attribute;
@@ -57,101 +61,123 @@
  */
 public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider {
 
-    private AtomicLong lastLdapUserSync = new AtomicLong(0L);
+	private final ScheduledExecutorService scheduledExecutorService;
 
 	public LdapAuthProvider() {
 		super("ldap");
+
+		scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
 	}
 
- 	private long getSynchronizationPeriod() {
-        final String cacheDuration = settings.getString(Keys.realm.ldap.ldapCachePeriod, "2 MINUTES");
+ 	private long getSynchronizationPeriodInMilliseconds() {
+ 		String period = settings.getString(Keys.realm.ldap.syncPeriod, null);
+ 		if (StringUtils.isEmpty(period)) {
+ 	 		period = settings.getString("realm.ldap.ldapCachePeriod", null);
+ 	 		if (StringUtils.isEmpty(period)) {
+ 	 			period = "5 MINUTES";
+ 	 		} else {
+ 	 			logger.warn("realm.ldap.ldapCachePeriod is obsolete!");
+ 	 			logger.warn(MessageFormat.format("Please set {0}={1} in gitblit.properties!", Keys.realm.ldap.syncPeriod, period));
+ 	 			settings.overrideSetting(Keys.realm.ldap.syncPeriod, period);
+ 	 		}
+ 		}
+
         try {
-            final String[] s = cacheDuration.split(" ", 2);
-            long duration = Long.parseLong(s[0]);
+            final String[] s = period.split(" ", 2);
+            long duration = Math.abs(Long.parseLong(s[0]));
             TimeUnit timeUnit = TimeUnit.valueOf(s[1]);
             return timeUnit.toMillis(duration);
         } catch (RuntimeException ex) {
-            throw new IllegalArgumentException(Keys.realm.ldap.ldapCachePeriod + " must have format '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'");
+            throw new IllegalArgumentException(Keys.realm.ldap.syncPeriod + " must have format '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'");
         }
     }
 
 	@Override
 	public void setup() {
-		synchronizeLdapUsers();
+		configureSyncService();
 	}
 
-	protected synchronized void synchronizeLdapUsers() {
-        final boolean enabled = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.enable, false);
-        if (enabled) {
-            if (System.currentTimeMillis() > (lastLdapUserSync.get() + getSynchronizationPeriod())) {
-            	logger.info("Synchronizing with LDAP @ " + settings.getRequiredString(Keys.realm.ldap.server));
-                final boolean deleteRemovedLdapUsers = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.removeDeleted, true);
-                LDAPConnection ldapConnection = getLdapConnection();
-                if (ldapConnection != null) {
-                    try {
-                        String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
-                        String uidAttribute = settings.getString(Keys.realm.ldap.uid, "uid");
-                        String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
-                        accountPattern = StringUtils.replace(accountPattern, "${username}", "*");
+	@Override
+	public void stop() {
+		scheduledExecutorService.shutdownNow();
+	}
 
-                        SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
-                        if (result != null && result.getEntryCount() > 0) {
-                            final Map<String, UserModel> ldapUsers = new HashMap<String, UserModel>();
+	public synchronized void sync() {
+		final boolean enabled = settings.getBoolean(Keys.realm.ldap.synchronize, false);
+		if (enabled) {
+			logger.info("Synchronizing with LDAP @ " + settings.getRequiredString(Keys.realm.ldap.server));
+			final boolean deleteRemovedLdapUsers = settings.getBoolean(Keys.realm.ldap.removeDeletedUsers, true);
+			LDAPConnection ldapConnection = getLdapConnection();
+			if (ldapConnection != null) {
+				try {
+					String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
+					String uidAttribute = settings.getString(Keys.realm.ldap.uid, "uid");
+					String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
+					accountPattern = StringUtils.replace(accountPattern, "${username}", "*");
 
-                            for (SearchResultEntry loggingInUser : result.getSearchEntries()) {
+					SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
+					if (result != null && result.getEntryCount() > 0) {
+						final Map<String, UserModel> ldapUsers = new HashMap<String, UserModel>();
 
-                                final String username = loggingInUser.getAttribute(uidAttribute).getValue();
-                                logger.debug("LDAP synchronizing: " + username);
+						for (SearchResultEntry loggingInUser : result.getSearchEntries()) {
+							Attribute uid = loggingInUser.getAttribute(uidAttribute);
+							if (uid == null) {
+								logger.error("Can not synchronize with LDAP, missing \"{}\" attribute", uidAttribute);
+								continue;
+							}
+							final String username = uid.getValue();
+							logger.debug("LDAP synchronizing: " + username);
 
-                                UserModel user = userManager.getUserModel(username);
-                                if (user == null) {
-                                    user = new UserModel(username);
-                                }
+							UserModel user = userManager.getUserModel(username);
+							if (user == null) {
+								user = new UserModel(username);
+							}
 
-                                if (!supportsTeamMembershipChanges()) {
-                                    getTeamsFromLdap(ldapConnection, username, loggingInUser, user);
-                                }
+							if (!supportsTeamMembershipChanges()) {
+								getTeamsFromLdap(ldapConnection, username, loggingInUser, user);
+							}
 
-                                // Get User Attributes
-                                setUserAttributes(user, loggingInUser);
+							// Get User Attributes
+							setUserAttributes(user, loggingInUser);
 
-                                // store in map
-                                ldapUsers.put(username.toLowerCase(), user);
-                            }
+							// store in map
+							ldapUsers.put(username.toLowerCase(), user);
+						}
 
-                            if (deleteRemovedLdapUsers) {
-                                logger.debug("detecting removed LDAP users...");
+						if (deleteRemovedLdapUsers) {
+							logger.debug("detecting removed LDAP users...");
 
-                                for (UserModel userModel : userManager.getAllUsers()) {
-                                    if (Constants.EXTERNAL_ACCOUNT.equals(userModel.password)) {
-                                        if (!ldapUsers.containsKey(userModel.username)) {
-                                            logger.info("deleting removed LDAP user " + userModel.username + " from user service");
-                                            userManager.deleteUser(userModel.username);
-                                        }
-                                    }
-                                }
-                            }
+							for (UserModel userModel : userManager.getAllUsers()) {
+								if (AccountType.LDAP == userModel.accountType) {
+									if (!ldapUsers.containsKey(userModel.username)) {
+										logger.info("deleting removed LDAP user " + userModel.username + " from user service");
+										userManager.deleteUser(userModel.username);
+									}
+								}
+							}
+						}
 
-                            userManager.updateUserModels(ldapUsers.values());
+						userManager.updateUserModels(ldapUsers.values());
 
-                            if (!supportsTeamMembershipChanges()) {
-                                final Map<String, TeamModel> userTeams = new HashMap<String, TeamModel>();
-                                for (UserModel user : ldapUsers.values()) {
-                                    for (TeamModel userTeam : user.teams) {
-                                        userTeams.put(userTeam.name, userTeam);
-                                    }
-                                }
-                                userManager.updateTeamModels(userTeams.values());
-                            }
-                        }
-                        lastLdapUserSync.set(System.currentTimeMillis());
-                    } finally {
-                        ldapConnection.close();
-                    }
-                }
-            }
-        }
-    }
+						if (!supportsTeamMembershipChanges()) {
+							final Map<String, TeamModel> userTeams = new HashMap<String, TeamModel>();
+							for (UserModel user : ldapUsers.values()) {
+								for (TeamModel userTeam : user.teams) {
+									userTeams.put(userTeam.name, userTeam);
+								}
+							}
+							userManager.updateTeamModels(userTeams.values());
+						}
+					}
+					if (!supportsTeamMembershipChanges()) {
+						getEmptyTeamsFromLdap(ldapConnection);
+					}
+				} finally {
+					ldapConnection.close();
+				}
+			}
+		}
+	}
 
 	private LDAPConnection getLdapConnection() {
 		try {
@@ -162,15 +188,20 @@
 			String bindUserName = settings.getString(Keys.realm.ldap.username, "");
 			String bindPassword = settings.getString(Keys.realm.ldap.password, "");
 
-
 			LDAPConnection conn;
 			if (ldapUrl.getScheme().equalsIgnoreCase("ldaps")) {
 				// SSL
 				SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
 				conn = new LDAPConnection(sslUtil.createSSLSocketFactory());
+				if (ldapPort == -1) {
+					ldapPort = 636;
+				}
 			} else if (ldapUrl.getScheme().equalsIgnoreCase("ldap") || ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {
 				// no encryption or StartTLS
 				conn = new LDAPConnection();
+				 if (ldapPort == -1) {
+					 ldapPort = 389;
+				 }
 			} else {
 				logger.error("Unsupported LDAP URL scheme: " + ldapUrl.getScheme());
 				return null;
@@ -187,7 +218,11 @@
 				}
 			}
 
-			if (!StringUtils.isEmpty(bindUserName) || !StringUtils.isEmpty(bindPassword)) {
+			if (StringUtils.isEmpty(bindUserName) && StringUtils.isEmpty(bindPassword)) {
+				// anonymous bind
+				conn.bind(new SimpleBindRequest());
+			} else {
+				// authenticated bind
 				conn.bind(new SimpleBindRequest(bindUserName, bindPassword));
 			}
 
@@ -238,7 +273,6 @@
 		return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.email, ""));
 	}
 
-
 	/**
 	 * If the LDAP server will maintain team memberships then LdapUserService
 	 * will not allow team membership changes.  In this scenario all team
@@ -250,6 +284,32 @@
 	@Override
 	public boolean supportsTeamMembershipChanges() {
 		return !settings.getBoolean(Keys.realm.ldap.maintainTeams, false);
+	}
+
+    @Override
+    public boolean supportsRoleChanges(UserModel user, Role role) {
+    	if (Role.ADMIN == role) {
+    		if (!supportsTeamMembershipChanges()) {
+    			List<String> admins = settings.getStrings(Keys.realm.ldap.admins);
+    			if (admins.contains(user.username)) {
+    				return false;
+    			}
+    		}
+    	}
+        return true;
+    }
+
+	@Override
+	public boolean supportsRoleChanges(TeamModel team, Role role) {
+		if (Role.ADMIN == role) {
+    		if (!supportsTeamMembershipChanges()) {
+    			List<String> admins = settings.getStrings(Keys.realm.ldap.admins);
+    			if (admins.contains("@" + team.name)) {
+    				return false;
+    			}
+    		}
+    	}
+		return true;
 	}
 
 	@Override
@@ -264,6 +324,20 @@
 		LDAPConnection ldapConnection = getLdapConnection();
 		if (ldapConnection != null) {
 			try {
+				boolean alreadyAuthenticated = false;
+
+				String bindPattern = settings.getString(Keys.realm.ldap.bindpattern, "");
+				if (!StringUtils.isEmpty(bindPattern)) {
+					try {
+						String bindUser = StringUtils.replace(bindPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
+						ldapConnection.bind(bindUser, new String(password));
+
+						alreadyAuthenticated = true;
+					} catch (LDAPException e) {
+						return 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}))");
@@ -274,22 +348,23 @@
 					SearchResultEntry loggingInUser = result.getSearchEntries().get(0);
 					String loggingInUserDN = loggingInUser.getDN();
 
-					if (isAuthenticated(ldapConnection, loggingInUserDN, new String(password))) {
+					if (alreadyAuthenticated || isAuthenticated(ldapConnection, loggingInUserDN, new String(password))) {
 						logger.debug("LDAP authenticated: " + username);
 
 						UserModel user = null;
 						synchronized (this) {
 							user = userManager.getUserModel(simpleUsername);
-							if (user == null)	// create user object for new authenticated user
+							if (user == null) {
+								// create user object for new authenticated user
 								user = new UserModel(simpleUsername);
-
-							// create a user cookie
-							if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
-								user.cookie = StringUtils.getSHA1(user.username + new String(password));
 							}
 
-							if (!supportsTeamMembershipChanges())
+							// create a user cookie
+							setCookie(user, password);
+
+							if (!supportsTeamMembershipChanges()) {
 								getTeamsFromLdap(ldapConnection, simpleUsername, loggingInUser, user);
+							}
 
 							// Get User Attributes
 							setUserAttributes(user, loggingInUser);
@@ -298,8 +373,9 @@
 							updateUser(user);
 
 							if (!supportsTeamMembershipChanges()) {
-								for (TeamModel userTeam : user.teams)
+								for (TeamModel userTeam : user.teams) {
 									updateTeam(userTeam);
+								}
 							}
 						}
 
@@ -328,12 +404,13 @@
 			if (!ArrayUtils.isEmpty(admins)) {
 				user.canAdmin = false;
 				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;
+					if (admin.startsWith("@") && user.isTeamMember(admin.substring(1))) {
+						// admin team
+						user.canAdmin = true;
+					} else if (user.getName().equalsIgnoreCase(admin)) {
+						// admin user
+						user.canAdmin = true;
+					}
 				}
 			}
 		}
@@ -352,9 +429,9 @@
 		if (!StringUtils.isEmpty(displayName)) {
 			// Replace embedded ${} with attributes
 			if (displayName.contains("${")) {
-				for (Attribute userAttribute : userEntry.getAttributes())
+				for (Attribute userAttribute : userEntry.getAttributes()) {
 					displayName = StringUtils.replace(displayName, "${" + userAttribute.getName() + "}", userAttribute.getValue());
-
+				}
 				user.displayName = displayName;
 			} else {
 				Attribute attribute = userEntry.getAttribute(displayName);
@@ -368,14 +445,18 @@
 		String email = settings.getString(Keys.realm.ldap.email, "");
 		if (!StringUtils.isEmpty(email)) {
 			if (email.contains("${")) {
-				for (Attribute userAttribute : userEntry.getAttributes())
+				for (Attribute userAttribute : userEntry.getAttributes()) {
 					email = StringUtils.replace(email, "${" + userAttribute.getName() + "}", userAttribute.getValue());
-
+				}
 				user.emailAddress = email;
 			} else {
 				Attribute attribute = userEntry.getAttribute(email);
 				if (attribute != null && attribute.hasValue()) {
 					user.emailAddress = attribute.getValue();
+				} else {
+					// issue-456/ticket-134
+					// allow LDAP to delete an email address
+					user.emailAddress = null;
 				}
 			}
 		}
@@ -384,7 +465,9 @@
 	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
+		// Clear the users team memberships - we're going to get them from LDAP
+		user.teams.clear();
+
 		String groupBase = settings.getString(Keys.realm.ldap.groupBase, "");
 		String groupMemberPattern = settings.getString(Keys.realm.ldap.groupMemberPattern, "(&(objectClass=group)(member=${dn}))");
 
@@ -411,6 +494,29 @@
 				teamModel.addUser(user.getName());
 			}
 		}
+	}
+
+	private void getEmptyTeamsFromLdap(LDAPConnection ldapConnection) {
+		logger.info("Start fetching empty teams from ldap.");
+		String groupBase = settings.getString(Keys.realm.ldap.groupBase, "");
+		String groupMemberPattern = settings.getString(Keys.realm.ldap.groupEmptyMemberPattern, "(&(objectClass=group)(!(member=*)))");
+
+		SearchResult teamMembershipResult = doSearch(ldapConnection, groupBase, true, groupMemberPattern, null);
+		if (teamMembershipResult != null && teamMembershipResult.getEntryCount() > 0) {
+			for (int i = 0; i < teamMembershipResult.getEntryCount(); i++) {
+				SearchResultEntry teamEntry = teamMembershipResult.getSearchEntries().get(i);
+				if (!teamEntry.hasAttribute("member")) {
+					String teamName = teamEntry.getAttribute("cn").getValue();
+
+					TeamModel teamModel = userManager.getTeamModel(teamName);
+					if (teamModel == null) {
+						teamModel = createTeamFromLdap(teamEntry);
+						userManager.updateTeamModel(teamModel);
+					}
+				}
+			}
+		}
+		logger.info("Finished fetching empty teams from ldap.");
 	}
 
 	private TeamModel createTeamFromLdap(SearchResultEntry teamEntry) {
@@ -505,4 +611,18 @@
 		}
 		return sb.toString();
 	}
+
+	private void configureSyncService() {
+		LdapSyncService ldapSyncService = new LdapSyncService(settings, this);
+		if (ldapSyncService.isReady()) {
+			long ldapSyncPeriod = getSynchronizationPeriodInMilliseconds();
+			int delay = 1;
+			logger.info("Ldap sync service will update users and groups every {} minutes.",
+					TimeUnit.MILLISECONDS.toMinutes(ldapSyncPeriod));
+			scheduledExecutorService.scheduleAtFixedRate(ldapSyncService, delay, ldapSyncPeriod,  TimeUnit.MILLISECONDS);
+		} else {
+			logger.info("Ldap sync service is disabled.");
+		}
+	}
+
 }

--
Gitblit v1.9.1