.gitignore
@@ -4,3 +4,4 @@ /build /keystore /gitblit.zip /gitblit-0.1.0-SNAPSHOT.zip build.xml
@@ -4,10 +4,33 @@ <!-- Project Properties --> <property name="project.jar" value="gitblit.jar" /> <property name="project.mainclass" value="com.gitblit.Launcher" /> <property name="distribution.zipfile" value="gitblit.zip" /> <property name="project.build.dir" value="${basedir}/build" /> <target name="main"> <!-- extract version number from source code --> <loadfile property="gb.version" srcfile="${basedir}/src/com/gitblit/Constants.java"> <filterchain> <linecontains> <contains value="public final static String VERSION = "/> </linecontains> <striplinebreaks/> <tokenfilter> <replacestring from="public final static String VERSION = "" to=""/> <replacestring from="";" to=""/> <trim /> </tokenfilter> </filterchain> </loadfile> <echo>Building Git:Blit ${gb.version}</echo> <!-- copy required distribution files to project folder --> <copy todir="${basedir}" overwrite="false"> <fileset dir="${basedir}/distrib"> <include name="gitblit.properties" /> <include name="users.properties" /> </fileset> </copy> <!-- Compile the build tool and execute it. This downloads missing compile-time dependencies from Maven. --> @@ -53,16 +76,13 @@ <mkdir dir="${basedir}/deploy" /> <copy todir="${basedir}/deploy" file="${project.jar}" /> <copy todir="${basedir}/deploy"> <fileset dir="${basedir}/service"> <fileset dir="${basedir}/distrib"> <include name="**/*" /> </fileset> <fileset dir="${basedir}"> <include name="*.cmd" /> <include name="*.properties" /> </fileset> </copy> <!-- Create Zip deployment --> <property name="distribution.zipfile" value="gitblit-${gb.version}.zip" /> <zip destfile="${distribution.zipfile}"> <fileset dir="${basedir}/deploy"> <include name="**/*" /> distrib/JavaService.exeBinary files differ
distrib/JavaService64.exeBinary files differ
distrib/UninstallService.bat
distrib/UninstallService64.bat
distrib/gitblit-stop.cmd
New file @@ -0,0 +1 @@ @java -jar gitblit.jar --stop distrib/gitblit.cmd
New file @@ -0,0 +1 @@ @java -jar gitblit.jar distrib/gitblit.properties
New file @@ -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!!!<a href="http://somehost/bug/$3">Bug-Id: $3</a> #regex.global.changeid = \\b(Change-Id:\\s*)([A-Za-z0-9]*)\\b!!!<a href="http://somehost/changeid/$2">Change-Id: $2</a> # Example per-repository regex substitutions overrides global #regex.myrepository.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!<a href="http://elsewhere/bug/$3">Bug-Id: $3</a> # # 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 distrib/installService.bat
distrib/installService64.bat
distrib/makekeystore_jdk.cmd
New file @@ -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" distrib/users.properties
New file @@ -0,0 +1,2 @@ # Git:Blit realm file format: username=password,\#permission,repository1,repository2... admin=admin,\#admin gitblit.properties
@@ -31,7 +31,11 @@ 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 src/com/gitblit/Constants.java
@@ -4,6 +4,8 @@ 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"; @@ -22,22 +24,16 @@ 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(); } } src/com/gitblit/GitBlit.java
@@ -96,9 +96,21 @@ response.addCookie(userCookie); } public UserModel getUser(String username) { public List<String> getAllUsernames() { return loginService.getAllUsernames(); } public UserModel getUserModel(String username) { UserModel user = loginService.getUserModel(username); return user; } public List<String> getRepositoryUsers(RepositoryModel repository) { return loginService.getUsernamesForRole(repository.name); } public boolean setRepositoryUsers(RepositoryModel repository, List<String> repositoryUsers) { return loginService.setUsernamesForRole(repository.name, repositoryUsers); } public void editUserModel(UserModel user, boolean isCreate) throws GitBlitException { @@ -206,7 +218,7 @@ } 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); src/com/gitblit/GitBlitServer.java
@@ -458,7 +458,7 @@ * 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 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 @@ boolean deleteUserModel(UserModel model); List<String> getAllUsernames(); List<String> getUsernamesForRole(String role); boolean setUsernamesForRole(String role, List<String> usernames); boolean renameRole(String oldRole, String newRole); boolean deleteRole(String role); } src/com/gitblit/JettyLoginService.java
@@ -5,9 +5,13 @@ 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,11 +20,15 @@ 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; @@ -44,8 +52,9 @@ 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,9 +84,20 @@ } 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; } @@ -85,15 +105,8 @@ @Override public boolean updateUserModel(UserModel model) { try { Properties properties = new Properties(); FileReader reader = new FileReader(realmFile); properties.load(reader); reader.close(); ArrayList<String> roles = new ArrayList<String>(); // Repositories roles.addAll(model.getRepositories()); Properties allUsers = readRealmFile(); ArrayList<String> roles = new ArrayList<String>(model.getRepositories()); // Permissions if (model.canAdmin()) { @@ -109,21 +122,15 @@ } // 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,27 +139,256 @@ 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<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); } 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!"); } } /* ------------------------------------------------------------ */ @@ -163,13 +399,10 @@ 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<Object, Object> entry : properties.entrySet()) { for (Map.Entry<Object, Object> entry : allUsers.entrySet()) { String username = ((String) entry.getKey()).trim(); String credentials = ((String) entry.getValue()).trim(); String roles = null; 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.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 @@ } } protected Map<AccessRestrictionType, String> getAccessRestrictions() { Map<AccessRestrictionType, String> map = new LinkedHashMap<AccessRestrictionType, String>(); 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(); } src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -80,9 +80,14 @@ 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 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 src/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -18,10 +18,11 @@ <tr><th><wicket:message key="gb.name"></wicket:message></th><td class="edit"><input type="text" wicket:id="name" id="name" size="40" tabindex="1" /></td></tr> <tr><th><wicket:message key="gb.description"></wicket:message></th><td class="edit"><input type="text" wicket:id="description" size="40" tabindex="2" /></td></tr> <tr><th><wicket:message key="gb.owner"></wicket:message></th><td class="edit"><input type="text" wicket:id="owner" size="40" tabindex="3" /></td></tr> <tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select wicket:id="accessRestriction" tabindex="4" /></td></tr> <tr><th><wicket:message key="gb.enableTickets"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useTickets" tabindex="5" /> <i>distributed Ticgit issues</i></td></tr> <tr><th><wicket:message key="gb.enableDocs"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useDocs" tabindex="6" /> <i>enumerates Markdown documentation in repository</i></td></tr> <tr><th><wicket:message key="gb.showRemoteBranches"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="showRemoteBranches" tabindex="7" /> <i>show remote branches</i></td></tr> <tr><th><wicket:message key="gb.enableTickets"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useTickets" tabindex="4" /> <i><wicket:message key="gb.useTicketsDescription"></wicket:message></i></td></tr> <tr><th><wicket:message key="gb.enableDocs"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useDocs" tabindex="5" /> <i><wicket:message key="gb.useDocsDescription"></wicket:message></i></td></tr> <tr><th><wicket:message key="gb.showRemoteBranches"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="showRemoteBranches" tabindex="6" /> <i><wicket:message key="gb.showRemoteBranchesDescription"></wicket:message></i></td></tr> <tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select wicket:id="accessRestriction" tabindex="7" /></td></tr> <tr><th style="vertical-align: top;"><wicket:message key="gb.permittedUsers"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr> <tr><th></th><td class="editButton"><input type="submit" value="Save" wicket:message="value:gb.save" tabindex="8" /></td></tr> </tbody> </table> 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 @@ } protected void setupPage(final RepositoryModel repositoryModel) { List<String> repositoryUsers = new ArrayList<String>(); 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<String> usersPalette = new Palette<String>("users", new ListModel<String>(repositoryUsers), new CollectionModel<String>(GitBlit.self().getAllUsernames()), new ChoiceRenderer<String>("", ""), 10, false); CompoundPropertyModel<RepositoryModel> model = new CompoundPropertyModel<RepositoryModel>(repositoryModel); Form<RepositoryModel> form = new Form<RepositoryModel>("editForm", model) { @@ -53,7 +70,48 @@ @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<String> users = usersPalette.getSelectedChoices(); List<String> repositoryUsers = new ArrayList<String>(); while (users.hasNext()) { repositoryUsers.add(users.next()); } GitBlit.self().setRepositoryUsers(repositoryModel, repositoryUsers); } } catch (GitBlitException e) { error(e.getMessage()); return; @@ -67,11 +125,33 @@ form.add(new TextField<String>("name").setEnabled(isCreate)); form.add(new TextField<String>("description")); form.add(new TextField<String>("owner")); form.add(new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays.asList(AccessRestrictionType.values()))); form.add(new DropDownChoice<AccessRestrictionType>("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<AccessRestrictionType> { private static final long serialVersionUID = 1L; private final Map<AccessRestrictionType, String> 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); } } } src/com/gitblit/wicket/pages/EditUserPage.html
@@ -18,8 +18,8 @@ <tr><th><wicket:message key="gb.name"></wicket:message></th><td class="edit"><input type="text" wicket:id="username" id="username" size="30" tabindex="1" /></td></tr> <tr><th><wicket:message key="gb.password"></wicket:message></th><td class="edit"><input type="password" wicket:id="password" size="30" tabindex="2" /></td></tr> <tr><th><wicket:message key="gb.confirmPassword"></wicket:message></th><td class="edit"><input type="password" wicket:id="confirmPassword" size="30" tabindex="3" /></td></tr> <tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> <i>can administer Git:Blit server</i></td></tr> <tr><th style="vertical-align: top;"><wicket:message key="gb.repositories"></wicket:message></th><td style="padding:2px;"><span wicket:id="repositories"></span></td></tr> <tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> <i><wicket:message key="gb.canAdminDescription"></wicket:message></i></td></tr> <tr><th style="vertical-align: top;"><wicket:message key="gb.restrictedRepositories"></wicket:message></th><td style="padding:2px;"><span wicket:id="repositories"></span></td></tr> <tr><th></th><td class="editButton"><input type="submit" value="Save" wicket:message="value:gb.save" tabindex="7" /></td></tr> </tbody> </table> src/com/gitblit/wicket/pages/EditUserPage.java
@@ -15,13 +15,18 @@ 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 @@ super(params); isCreate = false; String name = WicketUtils.getUsername(params); UserModel model = GitBlit.self().getUser(name); UserModel model = GitBlit.self().getUserModel(name); setupPage(model); } @@ -51,11 +56,16 @@ } else { super.setupPage("", getString("gb.edit")); } final Model<String> confirmPassword = new Model<String>(); final Model<String> confirmPassword = new Model<String>(StringUtils.isEmpty(userModel.getPassword()) ? "" : userModel.getPassword()); CompoundPropertyModel<UserModel> model = new CompoundPropertyModel<UserModel>(userModel); List<String> repos = GitBlit.self().getRepositoryList(); repos.add(0, "*"); // all repositories wildcard List<String> repos = new ArrayList<String>(); for (String repo : GitBlit.self().getRepositoryList()) { RepositoryModel repositoryModel = GitBlit.self().getRepositoryModel(repo); if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) { repos.add(repo); } } final Palette<String> repositories = new Palette<String>("repositories", new ListModel<String>(userModel.getRepositories()), new CollectionModel<String>(repos), new ChoiceRenderer<String>("", ""), 10, false); Form<UserModel> form = new Form<UserModel>("editForm", model) { @@ -67,7 +77,19 @@ error("Passwords do not match!"); return; } 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<String> selectedRepositories = repositories.getSelectedChoices(); List<String> repos = new ArrayList<String>(); @@ -82,14 +104,24 @@ return; } setRedirect(true); 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<String>("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); src/com/gitblit/wicket/pages/RepositoriesPage.html
@@ -31,7 +31,7 @@ <td><div class="list" wicket:id="repositoryName">[repository name]</div></td> <td><div class="list" wicket:id="repositoryDescription">[repository description]</div></td> <td class="author"><span wicket:id="repositoryOwner">[repository owner]</span></td> <td class="icon"><img wicket:id="ticketsIcon" /><img wicket:id="docsIcon" /><img wicket:id="restrictedAccessIcon" /></td> <td class="icon"><img wicket:id="ticketsIcon" /><img wicket:id="docsIcon" /><img wicket:id="accessRestrictionIcon" /></td> <td><span wicket:id="repositoryLastChange">[last change]</span></td> <td class="rightAlign"><span wicket:id="repositoryLinks"></span></td> </tr> src/com/gitblit/wicket/pages/RepositoriesPage.java
@@ -8,6 +8,7 @@ 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.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 @@ } add(repositoriesMessage); final Map<AccessRestrictionType, String> accessRestrictionTranslations = getAccessRestrictions(); UserModel user = GitBlitWebSession.get().getUser(); List<RepositoryModel> rows = GitBlit.self().getRepositoryModels(user); DataProvider dp = new DataProvider(rows); @@ -133,19 +136,19 @@ 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)); src/com/gitblit/wicket/pages/SummaryPage.html
@@ -20,7 +20,7 @@ <tr><th><wicket:message key="gb.owner">[owner]</wicket:message></th><td><span wicket:id="repositoryOwner">[repository owner]</span></td></tr> <tr><th><wicket:message key="gb.lastChange">[last change]</wicket:message></th><td><span wicket:id="repositoryLastChange">[repository last change]</span></td></tr> <tr><th><wicket:message key="gb.stats">[stats]</wicket:message></th><td><span wicket:id="repositoryStats">[repository stats]</span></td></tr> <tr><th><wicket:message key="gb.url">[URL]</wicket:message></th><td><span wicket:id="repositoryCloneUrl">[repository clone url]</span></td></tr> <tr><th><wicket:message key="gb.url">[URL]</wicket:message></th><td><img style="vertical-align: top; padding-right:5px;" wicket:id="accessRestrictionIcon" /><span wicket:id="repositoryCloneUrl">[repository clone url]</span></td></tr> </table> </div> </div> src/com/gitblit/wicket/pages/SummaryPage.java
@@ -19,6 +19,7 @@ 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 @@ } 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)); 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