From: James Moger Date: Thu, 12 May 2011 21:33:31 +0000 (-0400) Subject: Moved distribution files. Revised build script. Security revisions. X-Git-Tag: v0.5.0~52 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=f988253399ee475aa4f4e60adb95a220f8f88d21;p=gitblit.git Moved distribution files. Revised build script. Security revisions. --- diff --git a/.gitignore b/.gitignore index 10d4ae88..3ad2f7e8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /build /keystore /gitblit.zip +/gitblit-0.1.0-SNAPSHOT.zip diff --git a/build.xml b/build.xml index caaf3437..22f23360 100644 --- a/build.xml +++ b/build.xml @@ -3,12 +3,35 @@ - - + - + + + + + + + + + + + + + + + + Building Git:Blit ${gb.version} + + + + + + + + + @@ -53,16 +76,13 @@ - + - - - - + diff --git a/distrib/JavaService.exe b/distrib/JavaService.exe new file mode 100644 index 00000000..87559550 Binary files /dev/null and b/distrib/JavaService.exe differ diff --git a/distrib/JavaService64.exe b/distrib/JavaService64.exe new file mode 100644 index 00000000..fb327085 Binary files /dev/null and b/distrib/JavaService64.exe differ diff --git a/distrib/UninstallService.bat b/distrib/UninstallService.bat new file mode 100644 index 00000000..a483c55d --- /dev/null +++ b/distrib/UninstallService.bat @@ -0,0 +1 @@ +javaservice -uninstall gitblit \ No newline at end of file diff --git a/distrib/UninstallService64.bat b/distrib/UninstallService64.bat new file mode 100644 index 00000000..91f0c840 --- /dev/null +++ b/distrib/UninstallService64.bat @@ -0,0 +1 @@ +javaservice64 -uninstall gitblit \ No newline at end of file diff --git a/distrib/gitblit-stop.cmd b/distrib/gitblit-stop.cmd new file mode 100644 index 00000000..c139d57b --- /dev/null +++ b/distrib/gitblit-stop.cmd @@ -0,0 +1 @@ +@java -jar gitblit.jar --stop diff --git a/distrib/gitblit.cmd b/distrib/gitblit.cmd new file mode 100644 index 00000000..ce96a797 --- /dev/null +++ b/distrib/gitblit.cmd @@ -0,0 +1 @@ +@java -jar gitblit.jar diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties new file mode 100644 index 00000000..6bf33f13 --- /dev/null +++ b/distrib/gitblit.properties @@ -0,0 +1,159 @@ +# +# Git Servlet Settings +# + +# Allow push/pull over http/https with JGit servlet +git.enableGitServlet = true + +# Base folder for repositories +# Use forward slashes even on Windows!! +git.repositoriesFolder = c:/git + +# Export all repositories +# if false, each exported repository must have a .git/git-daemon-export-ok file +git.exportAll = true + +# Search repositories folder for nested repositories +# e.g. /libraries/mylibrary.git +git.nestedRepositories = true + +# The root clone url +git.cloneUrl = https://localhost/git/ + +# +# Authentication Settings +# + +# Require authentication to see everything but the admin pages +web.authenticateViewPages = false + +# Require admin authentication for the admin functions and pages +web.authenticateAdminPages = true + +# Simple user realm file to authenticate users +realm.realmFile = users.properties + +# How to store passwords. +# Valid values are plain, md5 or crypt (unix style). Default is md5. +realm.passwordStorage = md5 + +# +# Git:Blit Web Settings +# +# If blank Git:Blit is displayed. +web.siteName = + +# If web.authenticate=true, users with "admin" role can create repositories, +# create users, and edit repository metadata (owner, description, etc) +# +# If web.authenticate=false, any user can execute the aforementioned functions. +web.allowAdministration = true + +# This is the message display above the repositories table. +# This can point to a file with Markdown content. +# Specifying "gitblit" uses the internal welcome message. +web.repositoriesMessage = gitblit + +# Use the client timezone when formatting dates. +# This uses AJAX to determine the browser's timezone. +web.useClientTimezone = false + +# Date and Time formats +web.datestampShortFormat = yyyy-MM-dd +web.datetimestampLongFormat = EEEE, MMMM d, yyyy h:mm a z + +# Choose the diff presentation style: gitblt, gitweb, or plain +web.diffStyle = gitblit + +# Control if email addresses are shown in web ui +web.showEmailAddresses = true + +# Shows a combobox in the page links header with commit, committer, and author +# search selection. Default search is commit. +web.showSearchTypeSelection = false + +# Generates a line graph of repository activity over time on the Summary page. +# This is a real-time graph so generation may be expensive. +web.generateActivityGraph = true + +# The number of commits to display on the summary page +# Value must exceed 0 else default of 20 is used +web.summaryCommitCount = 16 + +# The number of tags/heads to display on the summary page +# Value must exceed 0 else default of 5 is used +web.summaryRefsCount = 5 + +# The number of items to show on a page before showing the first, prev, next +# pagination links. A default if 50 is used for any invalid value. +web.itemsPerPage = 50 + +# Registered extensions for google-code-prettify +web.prettyPrintExtensions = c cpp cs css htm html java js php pl prefs properties py rb sh sql xml vb + +# Registered extensions for markdown transformation +web.markdownExtensions = md mkd markdown + +# Image extensions +web.imageExtensions = bmp jpg gif png + +# Registered extensions for binary blobs +web.binaryExtensions = jar pdf tar.gz zip + +# Aggressive heap management will run the garbage collector on every generated +# page. This slows down page generation but improves heap consumption. +web.aggressiveHeapManagement = true + +# Run the webapp in debug mode +web.debugMode = false + +# Enable/disable global regex substitutions (i.e. shared across repositories) +regex.global = true + +# Example global regex substitutions +# Use !!! to separate the search pattern and the replace pattern +# searchpattern!!!replacepattern +#regex.global.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!Bug-Id: $3 +#regex.global.changeid = \\b(Change-Id:\\s*)([A-Za-z0-9]*)\\b!!!Change-Id: $2 + +# Example per-repository regex substitutions overrides global +#regex.myrepository.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!Bug-Id: $3 + +# +# Server Settings +# +server.tempFolder = temp +server.log4jPattern = %-5p %d{MM-dd HH:mm:ss.SSS} %-20.20c{1} %m%n +server.log4jPattern.windows = %-5p %m%n +server.log4jPattern.linux = + + +# +# Jetty Settings +# + +# Use Jetty NIO connectors. If false, Jetty Socket connectors will be used. +server.useNio = true + +# Standard http port to serve. <= 0 disables this connector. +server.httpPort = 0 + +# Secure/SSL https port to serve. <= 0 disables this connector. +server.httpsPort = 443 + +# Specify the interface for Jetty to bind the standard connector. +# You may specify an ip or an empty value to bind to all interfaces. +server.httpBindInterface = localhost + +# Specify the interface for Jetty to bind the secure connector. +# You may specify an ip or an empty value to bind to all interfaces. +server.httpsBindInterface = localhost + +# Password for SSL keystore. +# Keystore password and certificate password must match. +# This is provided for convenience, its probably more secure to set this value +# using the --storePassword command line parameter. +server.storePassword = dosomegit + +# Port for shutdown monitor to listen on. +server.shutdownPort = 8081 diff --git a/distrib/installService.bat b/distrib/installService.bat new file mode 100644 index 00000000..5b0df2c6 --- /dev/null +++ b/distrib/installService.bat @@ -0,0 +1,2 @@ +set JDK=C:\Program Files\Java\jdk1.6.0_21 +JavaService.exe -install gitblit "%JDK%\jre\bin\server\jvm.dll" -Xmx1024M -Djava.class.path=%CD%\gitblit.jar;"%JDK%\lib\tools.jar" -start com.gitblit.Launcher -params --storePassword dosomegit -stop com.gitblit.Launcher -params --stop -out %CD%\logs\stdout.log -err %CD%\logs\stderr.log -current %CD% \ No newline at end of file diff --git a/distrib/installService64.bat b/distrib/installService64.bat new file mode 100644 index 00000000..08761f86 --- /dev/null +++ b/distrib/installService64.bat @@ -0,0 +1,2 @@ +set JDK=C:\Program Files\Java\jdk1.6.0_21 +JavaService64.exe -install gitblit "%JDK%\jre\bin\server\jvm.dll" -Djava.class.path=%CD%\gitblit.jar;"%JDK%\lib\tools.jar" -start com.gitblit.Launcher -params --storePassword dosomegit -stop com.gitblit.Launcher -params --stop -out %CD%\logs\stdout.log -err %CD%\logs\stderr.log -current %CD% \ No newline at end of file diff --git a/distrib/makekeystore_jdk.cmd b/distrib/makekeystore_jdk.cmd new file mode 100644 index 00000000..34a11b21 --- /dev/null +++ b/distrib/makekeystore_jdk.cmd @@ -0,0 +1,2 @@ +@del keystore +@keytool -keystore keystore -alias localhost -genkey -keyalg RSA -dname "CN=localhost, OU=Git:Blit, O=Git:Blit, L=Some Town, ST=Some State, C=US" \ No newline at end of file diff --git a/distrib/users.properties b/distrib/users.properties new file mode 100644 index 00000000..920f3233 --- /dev/null +++ b/distrib/users.properties @@ -0,0 +1,2 @@ +# Git:Blit realm file format: username=password,\#permission,repository1,repository2... +admin=admin,\#admin diff --git a/gitblit.properties b/gitblit.properties index 2bdcf2c8..b9aae7cf 100644 --- a/gitblit.properties +++ b/gitblit.properties @@ -31,7 +31,11 @@ web.authenticateViewPages = false web.authenticateAdminPages = true # Simple user realm file to authenticate users -server.realmFile = users.properties +realm.realmFile = users.properties + +# How to store passwords. +# Valid values are plain, md5 or crypt (unix style). Default is md5. +realm.passwordStorage = md5 # # Git:Blit Web Settings diff --git a/service/JavaService.exe b/service/JavaService.exe deleted file mode 100644 index 87559550..00000000 Binary files a/service/JavaService.exe and /dev/null differ diff --git a/service/JavaService64.exe b/service/JavaService64.exe deleted file mode 100644 index fb327085..00000000 Binary files a/service/JavaService64.exe and /dev/null differ diff --git a/service/UninstallService.bat b/service/UninstallService.bat deleted file mode 100644 index a483c55d..00000000 --- a/service/UninstallService.bat +++ /dev/null @@ -1 +0,0 @@ -javaservice -uninstall gitblit \ No newline at end of file diff --git a/service/UninstallService64.bat b/service/UninstallService64.bat deleted file mode 100644 index 91f0c840..00000000 --- a/service/UninstallService64.bat +++ /dev/null @@ -1 +0,0 @@ -javaservice64 -uninstall gitblit \ No newline at end of file diff --git a/service/installService.bat b/service/installService.bat deleted file mode 100644 index 5b0df2c6..00000000 --- a/service/installService.bat +++ /dev/null @@ -1,2 +0,0 @@ -set JDK=C:\Program Files\Java\jdk1.6.0_21 -JavaService.exe -install gitblit "%JDK%\jre\bin\server\jvm.dll" -Xmx1024M -Djava.class.path=%CD%\gitblit.jar;"%JDK%\lib\tools.jar" -start com.gitblit.Launcher -params --storePassword dosomegit -stop com.gitblit.Launcher -params --stop -out %CD%\logs\stdout.log -err %CD%\logs\stderr.log -current %CD% \ No newline at end of file diff --git a/service/installService64.bat b/service/installService64.bat deleted file mode 100644 index 08761f86..00000000 --- a/service/installService64.bat +++ /dev/null @@ -1,2 +0,0 @@ -set JDK=C:\Program Files\Java\jdk1.6.0_21 -JavaService64.exe -install gitblit "%JDK%\jre\bin\server\jvm.dll" -Djava.class.path=%CD%\gitblit.jar;"%JDK%\lib\tools.jar" -start com.gitblit.Launcher -params --storePassword dosomegit -stop com.gitblit.Launcher -params --stop -out %CD%\logs\stdout.log -err %CD%\logs\stderr.log -current %CD% \ No newline at end of file diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java index 7d1758db..1b4a5188 100644 --- a/src/com/gitblit/Constants.java +++ b/src/com/gitblit/Constants.java @@ -4,6 +4,8 @@ public class Constants { public final static String NAME = "Git:Blit"; + // The build script extracts this exact line so be careful editing it + // and only use A-Z a-z 0-9 .-_ in the string. public final static String VERSION = "0.1.0-SNAPSHOT"; public final static String ADMIN_ROLE = "#admin"; @@ -21,23 +23,17 @@ public class Constants { } return NONE; } - + + public boolean exceeds(AccessRestrictionType type) { + return this.ordinal() > type.ordinal(); + } + public boolean atLeast(AccessRestrictionType type) { return this.ordinal() >= type.ordinal(); } public String toString() { - switch (this) { - case NONE: - return "Anonymous View, Clone, & Push"; - case PUSH: - return "Anonymous View & Clone, Authenticated Push"; - case CLONE: - return "Anonymous View, Authenticated Clone & Push"; - case VIEW: - return "Authenticated View, Clone, & Push"; - } - return "none"; + return name(); } } diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java index 40cb3886..62ff55eb 100644 --- a/src/com/gitblit/GitBlit.java +++ b/src/com/gitblit/GitBlit.java @@ -95,11 +95,23 @@ public class GitBlit implements ServletContextListener { userCookie.setPath("/"); response.addCookie(userCookie); } + + public List getAllUsernames() { + return loginService.getAllUsernames(); + } - public UserModel getUser(String username) { + public UserModel getUserModel(String username) { UserModel user = loginService.getUserModel(username); return user; } + + public List getRepositoryUsers(RepositoryModel repository) { + return loginService.getUsernamesForRole(repository.name); + } + + public boolean setRepositoryUsers(RepositoryModel repository, List repositoryUsers) { + return loginService.setUsernamesForRole(repository.name, repositoryUsers); + } public void editUserModel(UserModel user, boolean isCreate) throws GitBlitException { if (!loginService.updateUserModel(user)) { @@ -206,7 +218,7 @@ public class GitBlit implements ServletContextListener { } public void configureContext(IStoredSettings settings) { - logger.info("Configure GitBlit from " + settings.toString()); + logger.info("Using configuration from " + settings.toString()); this.storedSettings = settings; repositoriesFolder = new File(settings.getString(Keys.git.repositoriesFolder, "repos")); exportAll = settings.getBoolean(Keys.git.exportAll, true); diff --git a/src/com/gitblit/GitBlitServer.java b/src/com/gitblit/GitBlitServer.java index f5ed91aa..17b9e7a0 100644 --- a/src/com/gitblit/GitBlitServer.java +++ b/src/com/gitblit/GitBlitServer.java @@ -458,7 +458,7 @@ public class GitBlitServer { * Authentication Parameters */ @Parameter(names = { "--realmFile" }, description = "Users Realm Hash File") - public String realmFile = fileSettings.getString(Keys.server.realmFile, "users.properties"); + public String realmFile = fileSettings.getString(Keys.realm.realmFile, "users.properties"); /* * JETTY Parameters diff --git a/src/com/gitblit/ILoginService.java b/src/com/gitblit/ILoginService.java index d0c5d13d..242ff803 100644 --- a/src/com/gitblit/ILoginService.java +++ b/src/com/gitblit/ILoginService.java @@ -1,5 +1,7 @@ package com.gitblit; +import java.util.List; + import com.gitblit.wicket.models.UserModel; public interface ILoginService { @@ -14,4 +16,14 @@ public interface ILoginService { boolean deleteUserModel(UserModel model); + List getAllUsernames(); + + List getUsernamesForRole(String role); + + boolean setUsernamesForRole(String role, List usernames); + + boolean renameRole(String oldRole, String newRole); + + boolean deleteRole(String role); + } diff --git a/src/com/gitblit/JettyLoginService.java b/src/com/gitblit/JettyLoginService.java index 4b439647..fb510ee6 100644 --- a/src/com/gitblit/JettyLoginService.java +++ b/src/com/gitblit/JettyLoginService.java @@ -5,9 +5,13 @@ 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; @@ -16,12 +20,16 @@ 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.utils.StringUtils; import com.gitblit.wicket.models.UserModel; public class JettyLoginService extends MappedLoginService implements ILoginService { + private final Logger logger = LoggerFactory.getLogger(JettyLoginService.class); + private final File realmFile; public JettyLoginService(File realmFile) { @@ -44,8 +52,9 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi for (Principal principal : identity.getSubject().getPrincipals()) { if (principal instanceof RolePrincipal) { RolePrincipal role = (RolePrincipal) principal; - if (role.getName().charAt(0) != '#') { - user.addRepository(role.getName().substring(1)); + String roleName = role.getName(); + if (roleName.charAt(0) != '#') { + user.addRepository(roleName); } } } @@ -75,25 +84,29 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi } break; default: - model.addRepository(name.substring(1)); + 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.setPassword(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) { try { - Properties properties = new Properties(); - FileReader reader = new FileReader(realmFile); - properties.load(reader); - reader.close(); - - ArrayList roles = new ArrayList(); - - // Repositories - roles.addAll(model.getRepositories()); + Properties allUsers = readRealmFile(); + ArrayList roles = new ArrayList(model.getRepositories()); // Permissions if (model.canAdmin()) { @@ -109,21 +122,15 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi } // trim trailing comma sb.setLength(sb.length() - 1); + allUsers.put(model.getUsername(), sb.toString()); - // Update realm file - File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp"); - FileWriter writer = new FileWriter(realmFileCopy); - properties.put(model.getUsername(), sb.toString()); - properties.store(writer, null); - writer.close(); - realmFile.delete(); - realmFileCopy.renameTo(realmFile); + writeRealmFile(allUsers); // Update login service putUser(model.getUsername(), Credential.getCredential(model.getPassword()), roles.toArray(new String[0])); return true; } catch (Throwable t) { - t.printStackTrace(); + logger.error(MessageFormat.format("Failed to update user model {0}!", model.getUsername()), t); } return false; } @@ -132,29 +139,258 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi public boolean deleteUserModel(UserModel model) { try { // Read realm file - Properties properties = new Properties(); - FileReader reader = new FileReader(realmFile); - properties.load(reader); - reader.close(); - properties.remove(model.getUsername()); - - // Update realm file - File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp"); - FileWriter writer = new FileWriter(realmFileCopy); - properties.store(writer, null); - writer.close(); - realmFile.delete(); - realmFileCopy.renameTo(realmFile); + Properties allUsers = readRealmFile(); + allUsers.remove(model.getUsername()); + writeRealmFile(allUsers); // Drop user from map _users.remove(model.getUsername()); return true; } catch (Throwable t) { - t.printStackTrace(); + logger.error(MessageFormat.format("Failed to delete user model {0}!", model.getUsername()), t); + } + return false; + } + + @Override + public List getAllUsernames() { + List list = new ArrayList(); + list.addAll(_users.keySet()); + return list; + } + + @Override + public List getUsernamesForRole(String role) { + List list = new ArrayList(); + 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 usernames) { + try { + Set specifiedUsers = new HashSet(usernames); + Set needsAddRole = new HashSet(specifiedUsers); + Set needsRemoveRole = new HashSet(); + + // 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 revisedRoles = new ArrayList(); + // 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 needsRenameRole = new HashSet(); + + // 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 revisedRoles = new ArrayList(); + 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 needsDeleteRole = new HashSet(); + + // 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 revisedRoles = new ArrayList(); + // 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); + } 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) { + realmFile.delete(); + realmFileCopy.renameTo(realmFile); + } else { + throw new IOException("Failed to save realmfile!"); + } + } + /* ------------------------------------------------------------ */ @Override public void loadUsers() throws IOException { @@ -163,13 +399,10 @@ public class JettyLoginService extends MappedLoginService implements ILoginServi if (Log.isDebugEnabled()) Log.debug("Load " + this + " from " + realmFile); - Properties properties = new Properties(); - FileReader reader = new FileReader(realmFile); - properties.load(reader); - reader.close(); + Properties allUsers = readRealmFile(); // Map Users - for (Map.Entry entry : properties.entrySet()) { + for (Map.Entry entry : allUsers.entrySet()) { String username = ((String) entry.getKey()).trim(); String credentials = ((String) entry.getValue()).trim(); String roles = null; diff --git a/src/com/gitblit/wicket/BasePage.java b/src/com/gitblit/wicket/BasePage.java index 6125f2a0..733f4f7a 100644 --- a/src/com/gitblit/wicket/BasePage.java +++ b/src/com/gitblit/wicket/BasePage.java @@ -1,5 +1,7 @@ package com.gitblit.wicket; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.TimeZone; import javax.servlet.http.HttpServletRequest; @@ -14,6 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants; +import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.GitBlit; import com.gitblit.Keys; import com.gitblit.wicket.pages.SummaryPage; @@ -68,6 +71,27 @@ public abstract class BasePage extends WebPage { } } + protected Map getAccessRestrictions() { + Map map = new LinkedHashMap(); + for (AccessRestrictionType type : AccessRestrictionType.values()) { + switch (type) { + case NONE: + map.put(type, getString("gb.notRestricted")); + break; + case PUSH: + map.put(type, getString("gb.pushRestricted")); + break; + case CLONE: + map.put(type, getString("gb.cloneRestricted")); + break; + case VIEW: + map.put(type, getString("gb.viewRestricted")); + break; + } + } + return map; + } + protected TimeZone getTimeZone() { return GitBlit.self().settings().getBoolean(Keys.web.useClientTimezone, false) ? GitBlitWebSession.get().getTimezone() : TimeZone.getDefault(); } diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties index f2fe2327..3fe24d0a 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp.properties +++ b/src/com/gitblit/wicket/GitBlitWebApp.properties @@ -80,9 +80,14 @@ gb.showRemoteBranches = show remote branches gb.editUsers = edit users gb.password = password gb.confirmPassword = confirm password -gb.repositories = repositories +gb.restrictedRepositories = restricted repositories gb.canAdmin can admin -gb.notRestricted = open repository -gb.cloneRestricted = clone-restricted repository -gb.pushRestricted = push-restricted repository -gb.viewRestricted = view-restricted repository \ No newline at end of file +gb.notRestricted = anonymous view, clone, & push +gb.pushRestricted = authenticated push +gb.cloneRestricted = authenticated clone & push +gb.viewRestricted = authenticated view, clone, & push +gb.useTicketsDescription = distributed Ticgit issues +gb.useDocsDescription = enumerates Markdown documentation in repository +gb.showRemoteBranchesDescription = show remote branches +gb.canAdminDescription = can administer Git:Blit server +gb.permittedUsers = permitted users \ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/com/gitblit/wicket/pages/EditRepositoryPage.html index 58723475..db5ab229 100644 --- a/src/com/gitblit/wicket/pages/EditRepositoryPage.html +++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.html @@ -18,10 +18,11 @@ -  distributed Ticgit issues -  enumerates Markdown documentation in repository -  show remote branches +   +   +   + diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/com/gitblit/wicket/pages/EditRepositoryPage.java index 2d2b0ae2..8eed0059 100644 --- a/src/com/gitblit/wicket/pages/EditRepositoryPage.java +++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.java @@ -1,18 +1,29 @@ package com.gitblit.wicket.pages; +import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import org.apache.wicket.PageParameters; +import org.apache.wicket.extensions.markup.html.form.palette.Palette; import org.apache.wicket.markup.html.form.CheckBox; +import org.apache.wicket.markup.html.form.ChoiceRenderer; import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.IChoiceRenderer; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.util.CollectionModel; +import org.apache.wicket.model.util.ListModel; import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.GitBlit; import com.gitblit.GitBlitException; +import com.gitblit.utils.StringUtils; import com.gitblit.wicket.AdminPage; import com.gitblit.wicket.BasePage; import com.gitblit.wicket.WicketUtils; @@ -40,11 +51,17 @@ public class EditRepositoryPage extends BasePage { } protected void setupPage(final RepositoryModel repositoryModel) { + List repositoryUsers = new ArrayList(); if (isCreate) { super.setupPage("", getString("gb.newRepository")); } else { - super.setupPage("", getString("gb.edit") + " " + repositoryModel.name); + super.setupPage("", getString("gb.edit")); + if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) { + repositoryUsers.addAll(GitBlit.self().getRepositoryUsers(repositoryModel)); + } } + + final Palette usersPalette = new Palette("users", new ListModel(repositoryUsers), new CollectionModel(GitBlit.self().getAllUsernames()), new ChoiceRenderer("", ""), 10, false); CompoundPropertyModel model = new CompoundPropertyModel(repositoryModel); Form form = new Form("editForm", model) { @@ -53,7 +70,48 @@ public class EditRepositoryPage extends BasePage { @Override protected void onSubmit() { try { + // confirm a repository name was entered + if (StringUtils.isEmpty(repositoryModel.name)) { + error("Please set repository name!"); + return; + } + + // automatically convert backslashes to forward slashes + repositoryModel.name = repositoryModel.name.replace('\\', '/'); + + // confirm valid characters in repository name + char[] validChars = { '/', '.', '_', '-' }; + for (char c : repositoryModel.name.toCharArray()) { + if (!Character.isLetterOrDigit(c)) { + boolean ok = false; + for (char vc : validChars) { + ok |= c == vc; + } + if (!ok) { + error(MessageFormat.format("Illegal character '{0}' in repository name!", c)); + return; + } + } + } + + // confirm access restriction selection + if (repositoryModel.accessRestriction == null) { + error("Please select access restriction!"); + return; + } + + // save the repository GitBlit.self().editRepositoryModel(repositoryModel, isCreate); + + // save the repository access list + if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) { + Iterator users = usersPalette.getSelectedChoices(); + List repositoryUsers = new ArrayList(); + while (users.hasNext()) { + repositoryUsers.add(users.next()); + } + GitBlit.self().setRepositoryUsers(repositoryModel, repositoryUsers); + } } catch (GitBlitException e) { error(e.getMessage()); return; @@ -67,11 +125,33 @@ public class EditRepositoryPage extends BasePage { form.add(new TextField("name").setEnabled(isCreate)); form.add(new TextField("description")); form.add(new TextField("owner")); - form.add(new DropDownChoice("accessRestriction", Arrays.asList(AccessRestrictionType.values()))); + form.add(new DropDownChoice("accessRestriction", Arrays.asList(AccessRestrictionType.values()), new AccessRestrictionRenderer())); form.add(new CheckBox("useTickets")); form.add(new CheckBox("useDocs")); form.add(new CheckBox("showRemoteBranches")); + form.add(usersPalette); add(form); } + + private class AccessRestrictionRenderer implements IChoiceRenderer { + + private static final long serialVersionUID = 1L; + + private final Map map; + + public AccessRestrictionRenderer() { + map = getAccessRestrictions(); + } + + @Override + public String getDisplayValue(AccessRestrictionType type) { + return map.get(type); + } + + @Override + public String getIdValue(AccessRestrictionType type, int index) { + return Integer.toString(index); + } + } } diff --git a/src/com/gitblit/wicket/pages/EditUserPage.html b/src/com/gitblit/wicket/pages/EditUserPage.html index 57407d23..c50bdbac 100644 --- a/src/com/gitblit/wicket/pages/EditUserPage.html +++ b/src/com/gitblit/wicket/pages/EditUserPage.html @@ -18,8 +18,8 @@ -  can administer Git:Blit server - +   + diff --git a/src/com/gitblit/wicket/pages/EditUserPage.java b/src/com/gitblit/wicket/pages/EditUserPage.java index 250d1fde..d1faa782 100644 --- a/src/com/gitblit/wicket/pages/EditUserPage.java +++ b/src/com/gitblit/wicket/pages/EditUserPage.java @@ -15,13 +15,18 @@ import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.util.CollectionModel; import org.apache.wicket.model.util.ListModel; +import org.eclipse.jetty.http.security.Credential.Crypt; import org.eclipse.jetty.http.security.Credential.MD5; +import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.GitBlit; import com.gitblit.GitBlitException; +import com.gitblit.Keys; +import com.gitblit.utils.StringUtils; import com.gitblit.wicket.AdminPage; import com.gitblit.wicket.BasePage; import com.gitblit.wicket.WicketUtils; +import com.gitblit.wicket.models.RepositoryModel; import com.gitblit.wicket.models.UserModel; @AdminPage @@ -41,7 +46,7 @@ public class EditUserPage extends BasePage { super(params); isCreate = false; String name = WicketUtils.getUsername(params); - UserModel model = GitBlit.self().getUser(name); + UserModel model = GitBlit.self().getUserModel(name); setupPage(model); } @@ -51,12 +56,17 @@ public class EditUserPage extends BasePage { } else { super.setupPage("", getString("gb.edit")); } - final Model confirmPassword = new Model(); + final Model confirmPassword = new Model(StringUtils.isEmpty(userModel.getPassword()) ? "" : userModel.getPassword()); CompoundPropertyModel model = new CompoundPropertyModel(userModel); - List repos = GitBlit.self().getRepositoryList(); - repos.add(0, "*"); // all repositories wildcard - final Palette repositories = new Palette("repositories", new ListModel(userModel.getRepositories()), new CollectionModel(repos), new ChoiceRenderer("", ""), 10, false); + List repos = new ArrayList(); + for (String repo : GitBlit.self().getRepositoryList()) { + RepositoryModel repositoryModel = GitBlit.self().getRepositoryModel(repo); + if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) { + repos.add(repo); + } + } + final Palette repositories = new Palette("repositories", new ListModel(userModel.getRepositories()), new CollectionModel(repos), new ChoiceRenderer("", ""), 10, false); Form form = new Form("editForm", model) { private static final long serialVersionUID = 1L; @@ -67,8 +77,20 @@ public class EditUserPage extends BasePage { error("Passwords do not match!"); return; } - userModel.setPassword(MD5.digest(userModel.getPassword())); - + String password = userModel.getPassword(); + if (!password.toUpperCase().startsWith(Crypt.__TYPE) && !password.toUpperCase().startsWith(MD5.__TYPE)) { + // This is a plain text password. + // Optionally encrypt/obfuscate the password. + String type = GitBlit.self().settings().getString(Keys.realm.passwordStorage, "md5"); + if (type.equalsIgnoreCase("md5")) { + // store MD5 checksum of password + userModel.setPassword(MD5.digest(userModel.getPassword())); + } else if (type.equalsIgnoreCase("crypt")) { + // simple unix encryption + userModel.setPassword(Crypt.crypt(userModel.getUsername(), userModel.getPassword())); + } + } + Iterator selectedRepositories = repositories.getSelectedChoices(); List repos = new ArrayList(); while (selectedRepositories.hasNext()) { @@ -82,14 +104,24 @@ public class EditUserPage extends BasePage { return; } setRedirect(true); - setResponsePage(EditUserPage.class); + if (isCreate) { + // create another user + setResponsePage(EditUserPage.class); + } else { + // back to home + setResponsePage(RepositoriesPage.class); + } } }; // field names reflective match UserModel fields form.add(new TextField("username").setEnabled(isCreate)); - form.add(new PasswordTextField("password")); - form.add(new PasswordTextField("confirmPassword", confirmPassword)); + PasswordTextField passwordField = new PasswordTextField("password"); + passwordField.setResetPassword(false); + form.add(passwordField); + PasswordTextField confirmPasswordField = new PasswordTextField("confirmPassword", confirmPassword); + confirmPasswordField.setResetPassword(false); + form.add(confirmPasswordField); form.add(new CheckBox("canAdmin")); form.add(repositories); add(form); diff --git a/src/com/gitblit/wicket/pages/RepositoriesPage.html b/src/com/gitblit/wicket/pages/RepositoriesPage.html index 3016f648..c33e530d 100644 --- a/src/com/gitblit/wicket/pages/RepositoriesPage.html +++ b/src/com/gitblit/wicket/pages/RepositoriesPage.html @@ -31,7 +31,7 @@
[repository name]
[repository description]
[repository owner] - + [last change] diff --git a/src/com/gitblit/wicket/pages/RepositoriesPage.java b/src/com/gitblit/wicket/pages/RepositoriesPage.java index 45058296..acdc02fa 100644 --- a/src/com/gitblit/wicket/pages/RepositoriesPage.java +++ b/src/com/gitblit/wicket/pages/RepositoriesPage.java @@ -8,6 +8,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.Map; import org.apache.wicket.Component; import org.apache.wicket.PageParameters; @@ -23,6 +24,7 @@ import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.resource.ContextRelativeResource; +import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.GitBlit; import com.gitblit.Keys; import com.gitblit.utils.MarkdownUtils; @@ -99,6 +101,7 @@ public class RepositoriesPage extends BasePage { } add(repositoriesMessage); + final Map accessRestrictionTranslations = getAccessRestrictions(); UserModel user = GitBlitWebSession.get().getUser(); List rows = GitBlit.self().getRepositoryModels(user); DataProvider dp = new DataProvider(rows); @@ -130,22 +133,22 @@ public class RepositoriesPage extends BasePage { } else { item.add(WicketUtils.newBlankImage("docsIcon")); } - + switch (entry.accessRestriction) { case NONE: - item.add(WicketUtils.newBlankImage("restrictedAccessIcon")); + item.add(WicketUtils.newBlankImage("accessRestrictionIcon")); break; case PUSH: - item.add(WicketUtils.newImage("restrictedAccessIcon", "lock_go_16x16.png", getString("gb.pushRestricted"))); + item.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction))); break; case CLONE: - item.add(WicketUtils.newImage("restrictedAccessIcon", "lock_pull_16x16.png", getString("gb.cloneRestricted"))); + item.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction))); break; case VIEW: - item.add(WicketUtils.newImage("restrictedAccessIcon", "shield_16x16.png", getString("gb.viewRestricted"))); + item.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction))); break; default: - item.add(WicketUtils.newBlankImage("restrictedAccessIcon")); + item.add(WicketUtils.newBlankImage("accessRestrictionIcon")); } item.add(new Label("repositoryOwner", entry.owner)); diff --git a/src/com/gitblit/wicket/pages/SummaryPage.html b/src/com/gitblit/wicket/pages/SummaryPage.html index 6dbcfa22..35331f56 100644 --- a/src/com/gitblit/wicket/pages/SummaryPage.html +++ b/src/com/gitblit/wicket/pages/SummaryPage.html @@ -20,7 +20,7 @@ [owner][repository owner] [last change][repository last change] [stats][repository stats] - [URL][repository clone url] + [URL][repository clone url] diff --git a/src/com/gitblit/wicket/pages/SummaryPage.java b/src/com/gitblit/wicket/pages/SummaryPage.java index c0193dbe..d83f5961 100644 --- a/src/com/gitblit/wicket/pages/SummaryPage.java +++ b/src/com/gitblit/wicket/pages/SummaryPage.java @@ -19,6 +19,7 @@ import org.wicketstuff.googlecharts.LineStyle; import org.wicketstuff.googlecharts.MarkerType; import org.wicketstuff.googlecharts.ShapeMarker; +import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.GitBlit; import com.gitblit.Keys; import com.gitblit.utils.JGitUtils; @@ -66,6 +67,24 @@ public class SummaryPage extends RepositoryPage { } else { add(new Label("repositoryStats", MessageFormat.format("{0} commits and {1} tags in {2}", metricsTotal.count, metricsTotal.tag, TimeUtils.duration(metricsTotal.duration)))); } + + AccessRestrictionType accessRestriction = getRepositoryModel().accessRestriction; + switch (accessRestriction) { + case NONE: + add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false)); + break; + case PUSH: + add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png", getAccessRestrictions().get(accessRestriction))); + break; + case CLONE: + add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png", getAccessRestrictions().get(accessRestriction))); + break; + case VIEW: + add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png", getAccessRestrictions().get(accessRestriction))); + break; + default: + add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false)); + } add(new Label("repositoryCloneUrl", GitBlit.self().getCloneUrl(repositoryName))); add(new LogPanel("commitsPanel", repositoryName, null, r, numberCommits, 0)); diff --git a/users.properties b/users.properties index 0b22d827..920f3233 100644 --- a/users.properties +++ b/users.properties @@ -1,3 +1,2 @@ -#Wed May 11 21:30:28 EDT 2011 +# Git:Blit realm file format: username=password,\#permission,repository1,repository2... admin=admin,\#admin -test=test