From 27ae9095639bb228a1b7ff86a3ebe4264abf05be Mon Sep 17 00:00:00 2001
From: mschaefers <mschaefers@scoop-gmbh.de>
Date: Thu, 29 Nov 2012 12:33:09 -0500
Subject: [PATCH] feature: when using LdapUserService one can configure Gitblit to fetch all users from ldap that can possibly login. This allows to see newly generated LDAP users instantly in Gitblit. By now an LDAP user had to log in once to appear in GitBlit.

---
 src/com/gitblit/FileUserService.java |  332 +++++++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 285 insertions(+), 47 deletions(-)

diff --git a/src/com/gitblit/FileUserService.java b/src/com/gitblit/FileUserService.java
index 88c7d79..056df82 100644
--- a/src/com/gitblit/FileUserService.java
+++ b/src/com/gitblit/FileUserService.java
@@ -31,8 +31,10 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.gitblit.Constants.AccessPermission;
 import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
 import com.gitblit.utils.DeepCopier;
 import com.gitblit.utils.StringUtils;
 
@@ -73,6 +75,49 @@
 	}
 
 	/**
+	 * 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 user display name?
+	 * 
+	 * @return true or false
+	 * @since 1.0.0
+	 */
+	@Override
+	public boolean supportsDisplayNameChanges() {
+		return false;
+	}
+
+	/**
+	 * Does the user service support changes to user email address?
+	 * 
+	 * @return true or false
+	 * @since 1.0.0
+	 */
+	@Override
+	public boolean supportsEmailAddressChanges() {
+		return false;
+	}
+
+	/**
+	 * 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
@@ -89,13 +134,16 @@
 	 * @return cookie value
 	 */
 	@Override
-	public char[] getCookie(UserModel model) {
+	public String getCookie(UserModel model) {
+		if (!StringUtils.isEmpty(model.cookie)) {
+			return model.cookie;
+		}
 		Properties allUsers = super.read();
 		String value = allUsers.getProperty(model.username);
 		String[] roles = value.split(",");
 		String password = roles[0];
 		String cookie = StringUtils.getSHA1(model.username + password);
-		return cookie.toCharArray();
+		return cookie;
 	}
 
 	/**
@@ -156,6 +204,15 @@
 	}
 
 	/**
+	 * Logout a user.
+	 * 
+	 * @param user
+	 */
+	@Override
+	public void logout(UserModel user) {	
+	}
+
+	/**
 	 * Retrieve the user object for the specified username.
 	 * 
 	 * @param username
@@ -164,11 +221,11 @@
 	@Override
 	public UserModel getUserModel(String username) {
 		Properties allUsers = read();
-		String userInfo = allUsers.getProperty(username);
+		String userInfo = allUsers.getProperty(username.toLowerCase());
 		if (userInfo == null) {
 			return null;
 		}
-		UserModel model = new UserModel(username);
+		UserModel model = new UserModel(username.toLowerCase());
 		String[] userValues = userInfo.split(",");
 		model.password = userValues[0];
 		for (int i = 1; i < userValues.length; i++) {
@@ -178,12 +235,16 @@
 				// Permissions
 				if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) {
 					model.canAdmin = true;
+				} else if (role.equalsIgnoreCase(Constants.FORK_ROLE)) {
+					model.canFork = true;
+				} else if (role.equalsIgnoreCase(Constants.CREATE_ROLE)) {
+					model.canCreate = true;
 				} else if (role.equalsIgnoreCase(Constants.NOT_FEDERATED_ROLE)) {
 					model.excludeFromFederation = true;
 				}
 				break;
 			default:
-				model.addRepository(role);
+				model.addRepositoryPermission(role);
 			}
 		}
 		// set the teams for the user
@@ -207,6 +268,29 @@
 	}
 
 	/**
+	 * Updates/writes all specified user objects.
+	 * 
+	 * @param model a list of user models
+	 * @return true if update is successful
+	 * @since 1.2.0
+	 */
+	@Override
+	public boolean updateUserModels(List<UserModel> models) {
+		try {			
+			Properties allUsers = read();
+			for (UserModel model : models) {
+				updateUserCache(allUsers, model.username, model);
+			}
+			write(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to update {0} user models!", models.size()),
+					t);
+		}
+		return false;
+	}
+
+	/**
 	 * Updates/writes and replaces a complete user object keyed by username.
 	 * This method allows for renaming a user.
 	 * 
@@ -218,21 +302,63 @@
 	 */
 	@Override
 	public boolean updateUserModel(String username, UserModel model) {
-		try {
+		try {			
 			Properties allUsers = read();
+			updateUserCache(allUsers, username, model);
+			write(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
+					t);
+		}
+		return false;
+	}
+	
+	/**
+	 * Updates/writes and replaces a complete user object keyed by username.
+	 * This method allows for renaming a user.
+	 * 
+	 * @param username
+	 *            the old username
+	 * @param model
+	 *            the user object to use for username
+	 * @return true if update is successful
+	 */
+	private boolean updateUserCache(Properties allUsers, String username, UserModel model) {
+		try {			
 			UserModel oldUser = getUserModel(username);
-			ArrayList<String> roles = new ArrayList<String>(model.repositories);
+			List<String> roles;
+			if (model.permissions == null) {
+				roles = new ArrayList<String>();
+			} else {
+				// discrete repository permissions
+				roles = new ArrayList<String>();
+				for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
+					if (entry.getValue().exceeds(AccessPermission.NONE)) {
+						// code:repository (e.g. RW+:~james/myrepo.git
+						roles.add(entry.getValue().asRole(entry.getKey()));
+					}
+				}
+			}
 
 			// Permissions
 			if (model.canAdmin) {
 				roles.add(Constants.ADMIN_ROLE);
+			}
+			if (model.canFork) {
+				roles.add(Constants.FORK_ROLE);
+			}
+			if (model.canCreate) {
+				roles.add(Constants.CREATE_ROLE);
 			}
 			if (model.excludeFromFederation) {
 				roles.add(Constants.NOT_FEDERATED_ROLE);
 			}
 
 			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);
@@ -240,8 +366,8 @@
 			}
 			// trim trailing comma
 			sb.setLength(sb.length() - 1);
-			allUsers.remove(username);
-			allUsers.put(model.username, sb.toString());
+			allUsers.remove(username.toLowerCase());
+			allUsers.put(model.username.toLowerCase(), sb.toString());
 
 			// null check on "final" teams because JSON-sourced UserModel
 			// can have a null teams object
@@ -268,8 +394,6 @@
 					}
 				}
 			}
-
-			write(allUsers);
 			return true;
 		} catch (Throwable t) {
 			logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
@@ -334,6 +458,22 @@
 				continue;
 			}
 			list.add(user);
+		}
+		Collections.sort(list);
+		return list;
+	}
+
+	/**
+	 * Returns the list of all users available to the login service.
+	 * 
+	 * @return list of all usernames
+	 */
+	@Override
+	public List<UserModel> getAllUsers() {
+		read();
+		List<UserModel> list = new ArrayList<UserModel>();
+		for (String username : getAllUsernames()) {
+			list.add(getUserModel(username));
 		}
 		Collections.sort(list);
 		return list;
@@ -468,8 +608,8 @@
 				String[] roles = value.split(",");
 				// skip first value (password)
 				for (int i = 1; i < roles.length; i++) {
-					String r = roles[i];
-					if (r.equalsIgnoreCase(oldRole)) {
+					String repository = AccessPermission.repositoryFromRole(roles[i]);
+					if (repository.equalsIgnoreCase(oldRole)) {
 						needsRenameRole.add(username);
 						break;
 					}
@@ -489,9 +629,13 @@
 
 				// skip first value (password)
 				for (int i = 1; i < values.length; i++) {
-					String value = values[i];
-					if (!value.equalsIgnoreCase(oldRole)) {
-						sb.append(value);
+					String repository = AccessPermission.repositoryFromRole(values[i]);
+					if (repository.equalsIgnoreCase(oldRole)) {
+						AccessPermission permission = AccessPermission.permissionFromRole(values[i]);
+						sb.append(permission.asRole(newRole));
+						sb.append(',');
+					} else {
+						sb.append(values[i]);
 						sb.append(',');
 					}
 				}
@@ -528,9 +672,9 @@
 				String value = allUsers.getProperty(username);
 				String[] roles = value.split(",");
 				// skip first value (password)
-				for (int i = 1; i < roles.length; i++) {
-					String r = roles[i];
-					if (r.equalsIgnoreCase(role)) {
+				for (int i = 1; i < roles.length; i++) {					
+					String repository = AccessPermission.repositoryFromRole(roles[i]);
+					if (repository.equalsIgnoreCase(role)) {
 						needsDeleteRole.add(username);
 						break;
 					}
@@ -546,10 +690,10 @@
 				sb.append(password);
 				sb.append(',');
 				// skip first value (password)
-				for (int i = 1; i < values.length; i++) {
-					String value = values[i];
-					if (!value.equalsIgnoreCase(role)) {
-						sb.append(value);
+				for (int i = 1; i < values.length; i++) {					
+					String repository = AccessPermission.repositoryFromRole(values[i]);
+					if (!repository.equalsIgnoreCase(role)) {
+						sb.append(values[i]);
 						sb.append(',');
 					}
 				}
@@ -607,8 +751,9 @@
 	@Override
 	protected synchronized Properties read() {
 		long lastRead = lastModified();
+		boolean reload = forceReload();
 		Properties allUsers = super.read();
-		if (lastRead != lastModified()) {
+		if (reload || (lastRead != lastModified())) {
 			// reload hash cache
 			cookies.clear();
 			teams.clear();
@@ -634,17 +779,36 @@
 						} else if (role.charAt(0) == '%') {
 							postReceive.add(role.substring(1));
 						} else {
+							switch (role.charAt(0)) {
+							case '#':
+								// Permissions
+								if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) {
+									team.canAdmin = true;
+								} else if (role.equalsIgnoreCase(Constants.FORK_ROLE)) {
+									team.canFork = true;
+								} else if (role.equalsIgnoreCase(Constants.CREATE_ROLE)) {
+									team.canCreate = true;
+								}
+								break;
+							default:
+								repositories.add(role);
+							}
 							repositories.add(role);
 						}
 					}
-					team.addRepositories(repositories);
+					if (!team.canAdmin) {
+						// only read permissions for non-admin teams
+						team.addRepositoryPermissions(repositories);
+					}
 					team.addUsers(users);
 					team.addMailingLists(mailingLists);
+					team.preReceiveScripts.addAll(preReceive);
+					team.postReceiveScripts.addAll(postReceive);
 					teams.put(team.name.toLowerCase(), team);
 				} else {
 					// user definition
 					String password = roles[0];
-					cookies.put(StringUtils.getSHA1(username + password), username);
+					cookies.put(StringUtils.getSHA1(username.toLowerCase() + password), username.toLowerCase());
 				}
 			}
 		}
@@ -665,6 +829,20 @@
 	@Override
 	public List<String> getAllTeamNames() {
 		List<String> list = new ArrayList<String>(teams.keySet());
+		Collections.sort(list);
+		return list;
+	}
+
+	/**
+	 * Returns the list of all teams available to the login service.
+	 * 
+	 * @return list of all teams
+	 * @since 0.8.0
+	 */
+	@Override
+	public List<TeamModel> getAllTeams() {
+		List<TeamModel> list = new ArrayList<TeamModel>(teams.values());
+		list = DeepCopier.copy(list);
 		Collections.sort(list);
 		return list;
 	}
@@ -757,7 +935,7 @@
 			// remove role from team
 			for (String name : needsRemoveRole) {
 				String team = "@" + name;
-				String[] values = allUsers.getProperty(team).split(",");				
+				String[] values = allUsers.getProperty(team).split(",");
 				StringBuilder sb = new StringBuilder();
 				for (int i = 0; i < values.length; i++) {
 					String value = values[i];
@@ -811,6 +989,27 @@
 	public boolean updateTeamModel(TeamModel model) {
 		return updateTeamModel(model.name, model);
 	}
+	
+	/**
+	 * Updates/writes all specified team objects.
+	 * 
+	 * @param models a list of team models
+	 * @return true if update is successful
+	 * @since 1.2.0
+	 */
+	public boolean updateTeamModels(List<TeamModel> models) {
+		try {
+			Properties allUsers = read();
+			for (TeamModel model : models) {
+				updateTeamCache(allUsers, model.name, model);
+			}
+			write(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to update {0} team models!", models.size()), t);
+		}
+		return false;
+	}
 
 	/**
 	 * Updates/writes and replaces a complete team object keyed by teamname.
@@ -838,29 +1037,68 @@
 
 	private void updateTeamCache(Properties allUsers, String teamname, TeamModel model) {
 		StringBuilder sb = new StringBuilder();
-		for (String repository : model.repositories) {
-			sb.append(repository);
-			sb.append(',');
+		List<String> roles;
+		if (model.permissions == null) {
+			// legacy, use repository list
+			if (model.repositories != null) {
+				roles = new ArrayList<String>(model.repositories);
+			} else {
+				roles = new ArrayList<String>();
+			}
+		} else {
+			// discrete repository permissions
+			roles = new ArrayList<String>();
+			for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
+				if (entry.getValue().exceeds(AccessPermission.NONE)) {
+					// code:repository (e.g. RW+:~james/myrepo.git
+					roles.add(entry.getValue().asRole(entry.getKey()));
+				}
+			}
 		}
-		for (String user : model.users) {
-			sb.append('!');
-			sb.append(user);
-			sb.append(',');
+		
+		// Permissions
+		if (model.canAdmin) {
+			roles.add(Constants.ADMIN_ROLE);
 		}
-		for (String address : model.mailingLists) {
-			sb.append('&');
-			sb.append(address);
-			sb.append(',');
+		if (model.canFork) {
+			roles.add(Constants.FORK_ROLE);
 		}
-		for (String script : model.preReceiveScripts) {
-			sb.append('^');
-			sb.append(script);
-			sb.append(',');
+		if (model.canCreate) {
+			roles.add(Constants.CREATE_ROLE);
 		}
-		for (String script : model.postReceiveScripts) {
-			sb.append('%');
-			sb.append(script);
-			sb.append(',');
+
+		for (String role : roles) {
+				sb.append(role);
+				sb.append(',');
+		}
+		
+		if (!ArrayUtils.isEmpty(model.users)) {
+			for (String user : model.users) {
+				sb.append('!');
+				sb.append(user);
+				sb.append(',');
+			}
+		}
+		if (!ArrayUtils.isEmpty(model.mailingLists)) {
+			for (String address : model.mailingLists) {
+				sb.append('&');
+				sb.append(address);
+				sb.append(',');
+			}
+		}
+		if (!ArrayUtils.isEmpty(model.preReceiveScripts)) {
+			for (String script : model.preReceiveScripts) {
+				sb.append('^');
+				sb.append(script);
+				sb.append(',');
+			}
+		}
+		if (!ArrayUtils.isEmpty(model.postReceiveScripts)) {
+			for (String script : model.postReceiveScripts) {
+				sb.append('%');
+				sb.append(script);
+				sb.append(',');
+			}
 		}
 		// trim trailing comma
 		sb.setLength(sb.length() - 1);

--
Gitblit v1.9.1