From a1ea877042b93949ef244b96e8affd65cc3f89c1 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Wed, 01 Jun 2011 20:19:51 -0400
Subject: [PATCH] Readme markdown on summary page per-repository.

---
 src/com/gitblit/JettyLoginService.java |  465 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 452 insertions(+), 13 deletions(-)

diff --git a/src/com/gitblit/JettyLoginService.java b/src/com/gitblit/JettyLoginService.java
index 93e9a19..63a9861 100644
--- a/src/com/gitblit/JettyLoginService.java
+++ b/src/com/gitblit/JettyLoginService.java
@@ -1,32 +1,471 @@
+/*
+ * Copyright 2011 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 org.eclipse.jetty.security.HashLoginService;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.security.Principal;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.http.security.Credential;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.MappedLoginService;
 import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.log.Log;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import com.gitblit.wicket.User;
+import com.gitblit.models.UserModel;
 
-public class JettyLoginService extends HashLoginService implements ILoginService {
+public class JettyLoginService extends MappedLoginService implements ILoginService {
 
-	public JettyLoginService(String realmFile) {
-		super(Constants.NAME, realmFile);
+	private final Logger logger = LoggerFactory.getLogger(JettyLoginService.class);
+
+	private final File realmFile;
+
+	public JettyLoginService(File realmFile) {
+		super();
+		setName(Constants.NAME);
+		this.realmFile = realmFile;
 	}
-	
+
 	@Override
-	public User authenticate(String username, char[] password) {
+	public UserModel authenticate(String username, char[] password) {
 		UserIdentity identity = login(username, new String(password));
 		if (identity == null || identity.equals(UserIdentity.UNAUTHENTICATED_IDENTITY)) {
 			return null;
 		}
-		User user = new User(username, password);
-		user.canAdmin(identity.isUserInRole(Constants.ADMIN_ROLE, null));
-		user.canClone(identity.isUserInRole(Constants.PULL_ROLE, null));
-		user.canPush(identity.isUserInRole(Constants.PUSH_ROLE, null));
+		UserModel user = new UserModel(username);
+		user.canAdmin = identity.isUserInRole(Constants.ADMIN_ROLE, null);
+
+		// Add repositories
+		for (Principal principal : identity.getSubject().getPrincipals()) {
+			if (principal instanceof RolePrincipal) {
+				RolePrincipal role = (RolePrincipal) principal;
+				String roleName = role.getName();
+				if (roleName.charAt(0) != '#') {
+					user.addRepository(roleName);
+				}
+			}
+		}
 		return user;
 	}
 
 	@Override
-	public User authenticate(char [] cookie) {
-		// TODO cookie login
+	public UserModel getUserModel(String username) {
+		UserIdentity identity = _users.get(username);
+		if (identity == null) {
+			return null;
+		}
+		UserModel model = new UserModel(username);
+		Subject subject = identity.getSubject();
+		for (Principal principal : subject.getPrincipals()) {
+			if (principal instanceof RolePrincipal) {
+				RolePrincipal role = (RolePrincipal) principal;
+				String name = role.getName();
+				switch (name.charAt(0)) {
+				case '#':
+					// Permissions
+					if (name.equalsIgnoreCase(Constants.ADMIN_ROLE)) {
+						model.canAdmin = true;
+					}
+					break;
+				default:
+					model.addRepository(name);
+				}
+			}
+		}
+		// Retrieve the password from the realm file.
+		// Stupid, I know, but the password is buried within protected inner
+		// classes in private variables. Too much work to reflectively retrieve.
+		try {
+			Properties allUsers = readRealmFile();
+			String value = allUsers.getProperty(username);
+			String password = value.split(",")[0];
+			model.password = password;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to read password for user {0}!", username), t);
+		}
+		return model;
+	}
+
+	@Override
+	public boolean updateUserModel(UserModel model) {
+		return updateUserModel(model.username, model);
+	}
+
+	@Override
+	public boolean updateUserModel(String username, UserModel model) {
+		try {
+			Properties allUsers = readRealmFile();
+			ArrayList<String> roles = new ArrayList<String>(model.repositories);
+
+			// Permissions
+			if (model.canAdmin) {
+				roles.add(Constants.ADMIN_ROLE);
+			}
+
+			StringBuilder sb = new StringBuilder();
+			sb.append(model.password);
+			sb.append(',');
+			for (String role : roles) {
+				sb.append(role);
+				sb.append(',');
+			}
+			// trim trailing comma
+			sb.setLength(sb.length() - 1);
+			allUsers.remove(username);
+			allUsers.put(model.username, sb.toString());
+
+			writeRealmFile(allUsers);
+
+			// Update login service
+			removeUser(username);
+			putUser(model.username, Credential.getCredential(model.password),
+					roles.toArray(new String[0]));
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
+					t);
+		}
+		return false;
+	}
+
+	@Override
+	public boolean deleteUserModel(UserModel model) {
+		return deleteUser(model.username);
+	}
+
+	@Override
+	public boolean deleteUser(String username) {
+		try {
+			// Read realm file
+			Properties allUsers = readRealmFile();
+			allUsers.remove(username);
+			writeRealmFile(allUsers);
+
+			// Drop user from map
+			removeUser(username);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to delete user {0}!", username), t);
+		}
+		return false;
+	}
+
+	@Override
+	public List<String> getAllUsernames() {
+		List<String> list = new ArrayList<String>();
+		list.addAll(_users.keySet());
+		return list;
+	}
+
+	@Override
+	public List<String> getUsernamesForRole(String role) {
+		List<String> list = new ArrayList<String>();
+		try {
+			Properties allUsers = readRealmFile();
+			for (String username : allUsers.stringPropertyNames()) {
+				String value = allUsers.getProperty(username);
+				String[] values = value.split(",");
+				// skip first value (password)
+				for (int i = 1; i < values.length; i++) {
+					String r = values[i];
+					if (r.equalsIgnoreCase(role)) {
+						list.add(username);
+						break;
+					}
+				}
+			}
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t);
+		}
+		return list;
+	}
+
+	@Override
+	public boolean setUsernamesForRole(String role, List<String> usernames) {
+		try {
+			Set<String> specifiedUsers = new HashSet<String>(usernames);
+			Set<String> needsAddRole = new HashSet<String>(specifiedUsers);
+			Set<String> needsRemoveRole = new HashSet<String>();
+
+			// identify users which require add and remove role
+			Properties allUsers = readRealmFile();
+			for (String username : allUsers.stringPropertyNames()) {
+				String value = allUsers.getProperty(username);
+				String[] values = value.split(",");
+				// skip first value (password)
+				for (int i = 1; i < values.length; i++) {
+					String r = values[i];
+					if (r.equalsIgnoreCase(role)) {
+						// user has role, check against revised user list
+						if (specifiedUsers.contains(username)) {
+							needsAddRole.remove(username);
+						} else {
+							// remove role from user
+							needsRemoveRole.add(username);
+						}
+						break;
+					}
+				}
+			}
+
+			// add roles to users
+			for (String user : needsAddRole) {
+				String userValues = allUsers.getProperty(user);
+				userValues += "," + role;
+				allUsers.put(user, userValues);
+				String[] values = userValues.split(",");
+				String password = values[0];
+				String[] roles = new String[values.length - 1];
+				System.arraycopy(values, 1, roles, 0, values.length - 1);
+				putUser(user, Credential.getCredential(password), roles);
+			}
+
+			// remove role from user
+			for (String user : needsRemoveRole) {
+				String[] values = allUsers.getProperty(user).split(",");
+				String password = values[0];
+				StringBuilder sb = new StringBuilder();
+				sb.append(password);
+				sb.append(',');
+				List<String> revisedRoles = new ArrayList<String>();
+				// skip first value (password)
+				for (int i = 1; i < values.length; i++) {
+					String value = values[i];
+					if (!value.equalsIgnoreCase(role)) {
+						revisedRoles.add(value);
+						sb.append(value);
+						sb.append(',');
+					}
+				}
+				sb.setLength(sb.length() - 1);
+
+				// update properties
+				allUsers.put(user, sb.toString());
+
+				// update memory
+				putUser(user, Credential.getCredential(password),
+						revisedRoles.toArray(new String[0]));
+			}
+
+			// persist changes
+			writeRealmFile(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t);
+		}
+		return false;
+	}
+
+	@Override
+	public boolean renameRole(String oldRole, String newRole) {
+		try {
+			Properties allUsers = readRealmFile();
+			Set<String> needsRenameRole = new HashSet<String>();
+
+			// identify users which require role rename
+			for (String username : allUsers.stringPropertyNames()) {
+				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(oldRole)) {
+						needsRenameRole.remove(username);
+						break;
+					}
+				}
+			}
+
+			// rename role for identified users
+			for (String user : needsRenameRole) {
+				String userValues = allUsers.getProperty(user);
+				String[] values = userValues.split(",");
+				String password = values[0];
+				StringBuilder sb = new StringBuilder();
+				sb.append(password);
+				sb.append(',');
+				List<String> revisedRoles = new ArrayList<String>();
+				revisedRoles.add(newRole);
+				// skip first value (password)
+				for (int i = 1; i < values.length; i++) {
+					String value = values[i];
+					if (!value.equalsIgnoreCase(oldRole)) {
+						revisedRoles.add(value);
+						sb.append(value);
+						sb.append(',');
+					}
+				}
+				sb.setLength(sb.length() - 1);
+
+				// update properties
+				allUsers.put(user, sb.toString());
+
+				// update memory
+				putUser(user, Credential.getCredential(password),
+						revisedRoles.toArray(new String[0]));
+			}
+
+			// persist changes
+			writeRealmFile(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(
+					MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t);
+		}
+		return false;
+	}
+
+	@Override
+	public boolean deleteRole(String role) {
+		try {
+			Properties allUsers = readRealmFile();
+			Set<String> needsDeleteRole = new HashSet<String>();
+
+			// identify users which require role rename
+			for (String username : allUsers.stringPropertyNames()) {
+				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)) {
+						needsDeleteRole.remove(username);
+						break;
+					}
+				}
+			}
+
+			// delete role for identified users
+			for (String user : needsDeleteRole) {
+				String userValues = allUsers.getProperty(user);
+				String[] values = userValues.split(",");
+				String password = values[0];
+				StringBuilder sb = new StringBuilder();
+				sb.append(password);
+				sb.append(',');
+				List<String> revisedRoles = new ArrayList<String>();
+				// skip first value (password)
+				for (int i = 1; i < values.length; i++) {
+					String value = values[i];
+					if (!value.equalsIgnoreCase(role)) {
+						revisedRoles.add(value);
+						sb.append(value);
+						sb.append(',');
+					}
+				}
+				sb.setLength(sb.length() - 1);
+
+				// update properties
+				allUsers.put(user, sb.toString());
+
+				// update memory
+				putUser(user, Credential.getCredential(password),
+						revisedRoles.toArray(new String[0]));
+			}
+
+			// persist changes
+			writeRealmFile(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to delete role {0}!", role), t);
+		}
+		return false;
+	}
+
+	private Properties readRealmFile() throws IOException {
+		Properties allUsers = new Properties();
+		FileReader reader = new FileReader(realmFile);
+		allUsers.load(reader);
+		reader.close();
+		return allUsers;
+	}
+
+	private void writeRealmFile(Properties properties) throws IOException {
+		// Update realm file
+		File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp");
+		FileWriter writer = new FileWriter(realmFileCopy);
+		properties
+				.store(writer,
+						"# Git:Blit realm file format: username=password,\\#permission,repository1,repository2...");
+		writer.close();
+		if (realmFileCopy.exists() && realmFileCopy.length() > 0) {
+			if (realmFile.delete()) {
+				if (!realmFileCopy.renameTo(realmFile)) {
+					throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!",
+							realmFileCopy.getAbsolutePath(), realmFile.getAbsolutePath()));
+				}
+			} else {
+				throw new IOException(MessageFormat.format("Failed to delete (0)!",
+						realmFile.getAbsolutePath()));
+			}
+		} else {
+			throw new IOException(MessageFormat.format("Failed to save {0}!",
+					realmFileCopy.getAbsolutePath()));
+		}
+	}
+
+	/* ------------------------------------------------------------ */
+	@Override
+	public void loadUsers() throws IOException {
+		if (realmFile == null) {
+			return;
+		}
+
+		if (Log.isDebugEnabled()) {
+			Log.debug("Load " + this + " from " + realmFile);
+		}
+		Properties allUsers = readRealmFile();
+
+		// Map Users
+		for (Map.Entry<Object, Object> entry : allUsers.entrySet()) {
+			String username = ((String) entry.getKey()).trim();
+			String credentials = ((String) entry.getValue()).trim();
+			String roles = null;
+			int c = credentials.indexOf(',');
+			if (c > 0) {
+				roles = credentials.substring(c + 1).trim();
+				credentials = credentials.substring(0, c).trim();
+			}
+
+			if (username != null && username.length() > 0 && credentials != null
+					&& credentials.length() > 0) {
+				String[] roleArray = IdentityService.NO_ROLES;
+				if (roles != null && roles.length() > 0) {
+					roleArray = roles.split(",");
+				}
+				putUser(username, Credential.getCredential(credentials), roleArray);
+			}
+		}
+	}
+
+	@Override
+	protected UserIdentity loadUser(String username) {
 		return null;
 	}
 }

--
Gitblit v1.9.1