James Moger
2011-05-12 f988253399ee475aa4f4e60adb95a220f8f88d21
Moved distribution files. Revised build script. Security revisions.
5 files added
6 files renamed
19 files modified
800 ■■■■ changed files
.gitignore 1 ●●●● patch | view | raw | blame | history
build.xml 36 ●●●● patch | view | raw | blame | history
distrib/JavaService.exe patch | view | raw | blame | history
distrib/JavaService64.exe patch | view | raw | blame | history
distrib/UninstallService.bat patch | view | raw | blame | history
distrib/UninstallService64.bat patch | view | raw | blame | history
distrib/gitblit-stop.cmd 1 ●●●● patch | view | raw | blame | history
distrib/gitblit.cmd 1 ●●●● patch | view | raw | blame | history
distrib/gitblit.properties 159 ●●●●● patch | view | raw | blame | history
distrib/installService.bat patch | view | raw | blame | history
distrib/installService64.bat patch | view | raw | blame | history
distrib/makekeystore_jdk.cmd 2 ●●●●● patch | view | raw | blame | history
distrib/users.properties 2 ●●●●● patch | view | raw | blame | history
gitblit.properties 6 ●●●● patch | view | raw | blame | history
src/com/gitblit/Constants.java 20 ●●●●● patch | view | raw | blame | history
src/com/gitblit/GitBlit.java 16 ●●●● patch | view | raw | blame | history
src/com/gitblit/GitBlitServer.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/ILoginService.java 12 ●●●●● patch | view | raw | blame | history
src/com/gitblit/JettyLoginService.java 313 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/BasePage.java 24 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/GitBlitWebApp.properties 15 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditRepositoryPage.html 9 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditRepositoryPage.java 84 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditUserPage.html 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditUserPage.java 52 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/RepositoriesPage.html 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/RepositoriesPage.java 15 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/SummaryPage.html 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/SummaryPage.java 19 ●●●●● patch | view | raw | blame | history
users.properties 3 ●●●● patch | view | raw | blame | history
.gitignore
@@ -4,3 +4,4 @@
/build
/keystore
/gitblit.zip
/gitblit-0.1.0-SNAPSHOT.zip
build.xml
@@ -3,12 +3,35 @@
    <!-- 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.mainclass" value="com.gitblit.Launcher" />
    <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 = &quot;" to=""/>
                    <replacestring from="&quot;;" 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.exe
Binary files differ
distrib/JavaService64.exe
Binary 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";
@@ -21,23 +23,17 @@
            }
            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
@@ -95,10 +95,22 @@
        userCookie.setPath("/");
        response.addCookie(userCookie);
    }
    public List<String> getAllUsernames() {
        return loginService.getAllUsernames();
    }
    public UserModel getUser(String username) {
    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" /> &nbsp;<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" /> &nbsp;<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" /> &nbsp;<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" /> &nbsp;<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" /> &nbsp;<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" /> &nbsp;<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" /> &nbsp;<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" /> &nbsp;<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,12 +56,17 @@
        } 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
        final Palette<String> repositories = new Palette<String>("repositories", new ListModel<String>(userModel.getRepositories()), new CollectionModel<String>(repos), new ChoiceRenderer<String>("", ""), 10, false);
        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) {
            private static final long serialVersionUID = 1L;
@@ -67,8 +77,20 @@
                    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<String> selectedRepositories = repositories.getSelectedChoices();
                List<String> repos = new ArrayList<String>();
                while (selectedRepositories.hasNext()) {
@@ -82,14 +104,24 @@
                    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<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);
@@ -130,22 +133,22 @@
                } 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));
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