James Moger
2011-05-11 dfb88962fdbd29f59abe92178bb042738d57c3e1
Add user. Implemented repository view and git access restrictions.
1 files deleted
7 files added
19 files modified
840 ■■■■ changed files
gitblit.properties 14 ●●●● patch | view | raw | blame | history
src/com/gitblit/Constants.java 37 ●●●● patch | view | raw | blame | history
src/com/gitblit/GitBlit.java 45 ●●●● patch | view | raw | blame | history
src/com/gitblit/GitBlitServer.java 57 ●●●●● patch | view | raw | blame | history
src/com/gitblit/GitBlitServlet.java 84 ●●●●● patch | view | raw | blame | history
src/com/gitblit/ILoginService.java 9 ●●●● patch | view | raw | blame | history
src/com/gitblit/JettyLoginService.java 180 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/AuthorizationStrategy.java 1 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/GitBlitWebApp.properties 14 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/GitBlitWebSession.java 2 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/LoginPage.java 1 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/RepositoryPage.java 10 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/User.java 54 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/WicketUtils.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/models/RepositoryModel.java 11 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/models/User.java 89 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditRepositoryPage.html 16 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditRepositoryPage.java 9 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditUserPage.html 29 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditUserPage.java 97 ●●●●● 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 42 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/resources/gitblit.css 28 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/resources/lock_go_16x16.png patch | view | raw | blame | history
src/com/gitblit/wicket/resources/lock_pull_16x16.png patch | view | raw | blame | history
src/com/gitblit/wicket/resources/shield_16x16.png patch | view | raw | blame | history
users.properties 5 ●●●●● patch | view | raw | blame | history
gitblit.properties
@@ -3,7 +3,7 @@
#
# Allow push/pull over http/https with JGit servlet
git.allowPushPull = true
git.enableGitServlet = true
# Base folder for repositories
# Use forward slashes even on Windows!!
@@ -23,9 +23,6 @@
#
# Authentication Settings
#
# Require authentication for http/https push/pull access of git repositories
git.authenticate = true
# Require authentication to see everything but the admin pages
web.authenticateViewPages = false
@@ -131,11 +128,11 @@
# Jetty Settings
#
# use NIO connectors.  If false, socket connectors will be used.
# 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 = 80
server.httpPort = 0
# Secure/SSL https port to serve. <= 0 disables this connector.
server.httpsPort = 443
@@ -148,7 +145,10 @@
# 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)
# 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.
src/com/gitblit/Constants.java
@@ -6,14 +6,41 @@
    public final static String VERSION = "0.1.0-SNAPSHOT";
    public final static String ADMIN_ROLE = "admin";
    public final static String PULL_ROLE = "pull";
    public final static String PUSH_ROLE = "push";
    public final static String ADMIN_ROLE = "#admin";
    public final static String PROPERTIES_FILE = "gitblit.properties";
    public static enum AccessRestrictionType {
        NONE, PUSH, CLONE, VIEW;
        public static AccessRestrictionType fromString(String name) {
            for (AccessRestrictionType type : values()) {
                if (type.toString().equalsIgnoreCase(name)) {
                    return type;
                }
            }
            return NONE;
        }
        public boolean atLeast(AccessRestrictionType type) {
            return this.ordinal() >= type.ordinal();
        }
        public String toString() {
            switch (this) {
            case NONE:
                return "none";
            case PUSH:
                return "push";
            case CLONE:
                return "clone";
            case VIEW:
                return "view";
            }
            return "none";
        }
    }
    public static String getGitBlitVersion() {
        return NAME + " v" + VERSION;
    }
src/com/gitblit/GitBlit.java
@@ -19,9 +19,10 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.utils.JGitUtils;
import com.gitblit.wicket.User;
import com.gitblit.wicket.models.RepositoryModel;
import com.gitblit.wicket.models.User;
public class GitBlit implements ServletContextListener {
@@ -94,7 +95,18 @@
        userCookie.setPath("/");
        response.addCookie(userCookie);
    }
    public User getUser(String username) {
        User user = loginService.getUserModel(username);
        return user;
    }
    public void editUserModel(User user, boolean isCreate) throws GitBlitException {
        if (!loginService.updateUserModel(user)) {
            throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
        }
    }
    public List<String> getRepositoryList() {
        return JGitUtils.getRepositoryList(repositoriesFolder, exportAll, storedSettings.getBoolean(Keys.git.nestedRepositories, true));
    }
@@ -112,17 +124,31 @@
        }
        return r;
    }
    public List<RepositoryModel> getRepositoryModels() {
    public List<RepositoryModel> getRepositoryModels(User user) {
        List<String> list = getRepositoryList();
        List<RepositoryModel> repositories = new ArrayList<RepositoryModel>();
        for (String repo : list) {
            RepositoryModel model = getRepositoryModel(repo);
            repositories.add(model);
            RepositoryModel model = getRepositoryModel(user, repo);
            if (model != null) {
                repositories.add(model);
            }
        }
        return repositories;
    }
    
    public RepositoryModel getRepositoryModel(User user, String repositoryName) {
        RepositoryModel model = getRepositoryModel(repositoryName);
        if (model.accessRestriction.atLeast(AccessRestrictionType.VIEW)) {
            if (user != null && user.canView(model)) {
                return model;
            }
            return null;
        } else {
            return model;
        }
    }
    public RepositoryModel getRepositoryModel(String repositoryName) {
        Repository r = getRepository(repositoryName);
        RepositoryModel model = new RepositoryModel();
@@ -133,10 +159,9 @@
        if (config != null) {
            model.description = config.getString("gitblit", null, "description");
            model.owner = config.getString("gitblit", null, "owner");
            model.group = config.getString("gitblit", null, "group");
            model.useTickets = config.getBoolean("gitblit", "useTickets", false);
            model.useDocs = config.getBoolean("gitblit", "useDocs", false);
            model.useRestrictedAccess = config.getBoolean("gitblit", "restrictedAccess", false);
            model.accessRestriction = AccessRestrictionType.fromString(config.getString("gitblit", null, "accessRestriction"));
            model.showRemoteBranches = config.getBoolean("gitblit", "showRemoteBranches", false);
        }
        r.close();
@@ -149,7 +174,7 @@
            if (new File(repositoriesFolder, repository.name).exists()) {
                throw new GitBlitException(MessageFormat.format("Can not create repository {0} because it already exists.", repository.name));
            }
            // create repository
            // create repository
            logger.info("create repository " + repository.name);
            r = JGitUtils.createRepository(repositoriesFolder, repository.name, true);
        } else {
@@ -170,7 +195,7 @@
        config.setString("gitblit", null, "owner", repository.owner);
        config.setBoolean("gitblit", null, "useTickets", repository.useTickets);
        config.setBoolean("gitblit", null, "useDocs", repository.useDocs);
        config.setBoolean("gitblit", null, "restrictedAccess", repository.useRestrictedAccess);
        config.setString("gitblit", null, "accessRestriction", repository.accessRestriction.toString());
        config.setBoolean("gitblit", null, "showRemoteBranches", repository.showRemoteBranches);
        try {
            config.save();
src/com/gitblit/GitBlitServer.java
@@ -56,7 +56,6 @@
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jgit.http.server.GitServlet;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
@@ -222,44 +221,42 @@
        // Git Servlet
        ServletHolder gitServlet = null;
        String gitServletPathSpec = "/git/*";
        if (fileSettings.getBoolean(Keys.git.allowPushPull, true)) {
            gitServlet = rootContext.addServlet(GitServlet.class, gitServletPathSpec);
        if (fileSettings.getBoolean(Keys.git.enableGitServlet, true)) {
            gitServlet = rootContext.addServlet(GitBlitServlet.class, gitServletPathSpec);
            gitServlet.setInitParameter("base-path", params.repositoriesFolder);
            gitServlet.setInitParameter("export-all", params.exportAll ? "1" : "0");
            gitServlet.setInitParameter("export-all", fileSettings.getBoolean(Keys.git.exportAll, true) ? "1" : "0");
        }
        // Login Service
        LoginService loginService = null;
        String realmUsers = params.realmFile;
        if (realmUsers != null && new File(realmUsers).exists()) {
            logger.info("Setting up login service from " + realmUsers);
            JettyLoginService jettyLoginService = new JettyLoginService(realmUsers);
            GitBlit.self().setLoginService(jettyLoginService);
            loginService = jettyLoginService;
        String realmUsers = params.realmFile;
        if (!StringUtils.isEmpty(realmUsers)) {
            File realmFile = new File(realmUsers);
            if (realmFile.exists()) {
                logger.info("Setting up login service from " + realmUsers);
                JettyLoginService jettyLoginService = new JettyLoginService(realmFile);
                GitBlit.self().setLoginService(jettyLoginService);
                loginService = jettyLoginService;
            }
        }
        // Determine what handler to use
        Handler handler;
        if (gitServlet != null) {
            if (loginService != null && params.authenticatePushPull) {
                // Authenticate Pull/Push
                String[] roles = new String[] { Constants.PULL_ROLE, Constants.PUSH_ROLE };
                logger.info("Authentication required for git servlet pull/push access");
            if (loginService != null) {
                // Authenticate Clone/Push
                logger.info("Setting up authenticated git servlet clone/push access");
                Constraint constraint = new Constraint();
                constraint.setName("auth");
                constraint.setAuthenticate(true);
                constraint.setRoles(roles);
                constraint.setRoles(new String [] { "*" });
                ConstraintMapping mapping = new ConstraintMapping();
                mapping.setPathSpec(gitServletPathSpec);
                mapping.setConstraint(constraint);
                ConstraintSecurityHandler security = new ConstraintSecurityHandler();
                ConstraintSecurityHandler security = new ConstraintSecurityHandler();
                security.addConstraintMapping(mapping);
                for (String role : roles) {
                    security.addRole(role);
                }
                security.setAuthenticator(new BasicAuthenticator());
                security.setLoginService(loginService);
                security.setStrict(false);
@@ -273,7 +270,7 @@
                handler = rootContext;
            }
        } else {
            logger.info("Git servlet pull/push disabled");
            logger.info("Git servlet clone/push disabled");
            handler = rootContext;
        }
@@ -448,37 +445,31 @@
        @Parameter(names = { "--stop" }, description = "Stop Server")
        public Boolean stop = false;
        @Parameter(names = { "--temp" }, description = "Server temp folder")
        @Parameter(names = { "--tempFolder" }, description = "Server temp folder")
        public String temp = fileSettings.getString(Keys.server.tempFolder, "temp");
        /*
         * GIT Servlet Parameters
         */
        @Parameter(names = { "--repos" }, description = "Git Repositories Folder")
        @Parameter(names = { "--repositoriesFolder" }, description = "Git Repositories Folder")
        public String repositoriesFolder = fileSettings.getString(Keys.git.repositoriesFolder, "repos");
        @Parameter(names = { "--exportAll" }, description = "Export All Found Repositories")
        public Boolean exportAll = fileSettings.getBoolean(Keys.git.exportAll, true);
        /*
         * Authentication Parameters
         */
        @Parameter(names = { "--authenticatePushPull" }, description = "Authenticate Git Push/Pull access")
        public Boolean authenticatePushPull = fileSettings.getBoolean(Keys.git.authenticate, true);
        @Parameter(names = { "--realm" }, description = "Users Realm Hash File")
        @Parameter(names = { "--realmFile" }, description = "Users Realm Hash File")
        public String realmFile = fileSettings.getString(Keys.server.realmFile, "users.properties");
        /*
         * JETTY Parameters
         */
        @Parameter(names = { "--nio" }, description = "Use NIO Connector else use Socket Connector.")
        @Parameter(names = { "--useNio" }, description = "Use NIO Connector else use Socket Connector.")
        public Boolean useNIO = fileSettings.getBoolean(Keys.server.useNio, true);
        @Parameter(names = "--port", description = "HTTP port for to serve. (port <= 0 will disable this connector)")
        @Parameter(names = "--httpPort", description = "HTTP port for to serve. (port <= 0 will disable this connector)")
        public Integer port = fileSettings.getInteger(Keys.server.httpPort, 80);
        @Parameter(names = "--securePort", description = "HTTPS port to serve.  (port <= 0 will disable this connector)")
        @Parameter(names = "--httpsPort", description = "HTTPS port to serve.  (port <= 0 will disable this connector)")
        public Integer securePort = fileSettings.getInteger(Keys.server.httpsPort, 443);
        @Parameter(names = "--storePassword", description = "Password for SSL (https) keystore.")
src/com/gitblit/GitBlitServlet.java
New file
@@ -0,0 +1,84 @@
package com.gitblit;
import java.io.IOException;
import java.text.MessageFormat;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.http.server.GitServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.wicket.models.RepositoryModel;
public class GitBlitServlet extends GitServlet {
    private static final long serialVersionUID = 1L;
    private final Logger logger = LoggerFactory.getLogger(GitBlitServlet.class);
    public GitBlitServlet() {
        super();
    }
    @Override
    protected void service(final HttpServletRequest req, final HttpServletResponse rsp) throws ServletException, IOException {
        // admins have full git access to all repositories
        if (req.isUserInRole(Constants.ADMIN_ROLE)) {
            // admins can do whatever
            super.service(req, rsp);
            return;
        }
        // try to intercept repository names for authenticated access
        String url = req.getRequestURI().substring(req.getServletPath().length());
        if (url.charAt(0) == '/' && url.length() > 1) {
            url = url.substring(1);
        }
        int forwardSlash = url.indexOf('/');
        if (forwardSlash > -1) {
            String repository = url.substring(0, forwardSlash);
            String function = url.substring(forwardSlash + 1);
            String query = req.getQueryString();
            RepositoryModel model = GitBlit.self().getRepositoryModel(repository);
            if (model != null) {
                if (model.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
                    boolean authorizedUser = req.isUserInRole(repository);
                    if (function.startsWith("git-receive-pack") || (query.indexOf("service=git-receive-pack") > -1)) {
                        // Push request
                        boolean pushRestricted = model.accessRestriction.atLeast(AccessRestrictionType.PUSH);
                        if (!pushRestricted || (pushRestricted && authorizedUser)) {
                            // push-unrestricted or push-authorized
                            super.service(req, rsp);
                            return;
                        } else {
                            // user is unauthorized to push to this repository
                            logger.warn(MessageFormat.format("user {0} is not authorized to push to {1} ", req.getUserPrincipal().getName(), repository));
                            rsp.sendError(HttpServletResponse.SC_FORBIDDEN, MessageFormat.format("you are not authorized to push to {0} ", repository));
                            return;
                        }
                    } else if (function.startsWith("git-upload-pack") || (query.indexOf("service=git-upload-pack") > -1)) {
                        // Clone request
                        boolean cloneRestricted = model.accessRestriction.atLeast(AccessRestrictionType.CLONE);
                        if (!cloneRestricted || (cloneRestricted && authorizedUser)) {
                            // clone-unrestricted or clone-authorized
                            super.service(req, rsp);
                            return;
                        } else {
                            // user is unauthorized to clone this repository
                            logger.warn(MessageFormat.format("user {0} is not authorized to clone {1} ", req.getUserPrincipal().getName(), repository));
                            rsp.sendError(HttpServletResponse.SC_FORBIDDEN, MessageFormat.format("you are not authorized to clone {0} ", repository));
                            return;
                        }
                    }
                }
            }
        }
        // pass-through to git servlet
        super.service(req, rsp);
    }
}
src/com/gitblit/ILoginService.java
@@ -1,10 +1,17 @@
package com.gitblit;
import com.gitblit.wicket.User;
import com.gitblit.wicket.models.User;
public interface ILoginService {
    User authenticate(String username, char[] password);
    User authenticate(char[] cookie);
    User getUserModel(String username);
    boolean updateUserModel(User model);
    boolean deleteUserModel(User model);
}
src/com/gitblit/JettyLoginService.java
@@ -1,14 +1,33 @@
package com.gitblit;
import org.eclipse.jetty.security.HashLoginService;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Map;
import java.util.Properties;
import javax.security.auth.Subject;
import org.eclipse.jetty.http.security.Credential;
import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.MappedLoginService;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.log.Log;
import com.gitblit.wicket.User;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.models.User;
public class JettyLoginService extends HashLoginService implements ILoginService {
public class JettyLoginService extends MappedLoginService implements ILoginService {
    public JettyLoginService(String realmFile) {
        super(Constants.NAME, realmFile);
    private final File realmFile;
    public JettyLoginService(File realmFile) {
        super();
        setName(Constants.NAME);
        this.realmFile = realmFile;
    }
    @Override
@@ -17,10 +36,19 @@
        if (identity == null || identity.equals(UserIdentity.UNAUTHENTICATED_IDENTITY)) {
            return null;
        }
        User user = new User(username, password);
        User user = new User(username);
        user.setCookie(StringUtils.getSHA1((Constants.NAME + username + new String(password))));
        user.canAdmin(identity.isUserInRole(Constants.ADMIN_ROLE, null));
        user.canClone(identity.isUserInRole(Constants.PULL_ROLE, null));
        user.canPush(identity.isUserInRole(Constants.PUSH_ROLE, null));
        // Add repositories
        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));
                }
            }
        }
        return user;
    }
@@ -29,4 +57,140 @@
        // TODO cookie login
        return null;
    }
    @Override
    public User getUserModel(String username) {
        User model = new User(username);
        UserIdentity identity = _users.get(username);
        Subject subject = identity.getSubject();
        for (Principal principal : subject.getPrincipals()) {
            if (principal instanceof RolePrincipal) {
                RolePrincipal role = (RolePrincipal) principal;
                String name = role.getName();
                switch (name.charAt(0)) {
                case '#':
                    // Permissions
                    if (name.equalsIgnoreCase(Constants.ADMIN_ROLE)) {
                        model.canAdmin(true);
                    }
                    break;
                default:
                    model.addRepository(name.substring(1));
                }
            }
        }
        return model;
    }
    @Override
    public boolean updateUserModel(User 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());
            // Permissions
            if (model.canAdmin()) {
                roles.add(Constants.ADMIN_ROLE);
            }
            StringBuilder sb = new StringBuilder();
            sb.append(model.getPassword());
            sb.append(',');
            for (String role : roles) {
                sb.append(role);
                sb.append(',');
            }
            // trim trailing comma
            sb.setLength(sb.length() - 1);
            // 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);
            // Update login service
            putUser(model.getUsername(), Credential.getCredential(model.getPassword()), roles.toArray(new String[0]));
            return true;
        } catch (Throwable t) {
            t.printStackTrace();
        }
        return false;
    }
    @Override
    public boolean deleteUserModel(User 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);
            // Drop user from map
            _users.remove(model.getUsername());
            return true;
        } catch (Throwable t) {
            t.printStackTrace();
        }
        return false;
    }
    /* ------------------------------------------------------------ */
    @Override
    public void loadUsers() throws IOException {
        if (realmFile == null)
            return;
        if (Log.isDebugEnabled())
            Log.debug("Load " + this + " from " + realmFile);
        Properties properties = new Properties();
        FileReader reader = new FileReader(realmFile);
        properties.load(reader);
        reader.close();
        // Map Users
        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
            String username = ((String) entry.getKey()).trim();
            String credentials = ((String) entry.getValue()).trim();
            String roles = null;
            int c = credentials.indexOf(',');
            if (c > 0) {
                roles = credentials.substring(c + 1).trim();
                credentials = credentials.substring(0, c).trim();
            }
            if (username != null && username.length() > 0 && credentials != null && credentials.length() > 0) {
                String[] roleArray = IdentityService.NO_ROLES;
                if (roles != null && roles.length() > 0) {
                    roleArray = roles.split(",");
                }
                putUser(username, Credential.getCredential(credentials), roleArray);
            }
        }
    }
    @Override
    protected UserIdentity loadUser(String username) {
        return null;
    }
}
src/com/gitblit/wicket/AuthorizationStrategy.java
@@ -7,6 +7,7 @@
import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.wicket.models.User;
import com.gitblit.wicket.pages.RepositoriesPage;
public class AuthorizationStrategy extends AbstractPageAuthorizationStrategy implements IUnauthorizedComponentInstantiationListener {
src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -70,11 +70,19 @@
gb.rename = rename
gb.delete = delete
gb.docs = docs
gb.restrictedAccess = restricted access
gb.accessRestriction = access restriction
gb.name = name
gb.group = group
gb.description = description
gb.enableTickets = enable tickets
gb.enableDocs = enable docs
gb.save = save
gb.showRemoteBranches = show remote branches
gb.showRemoteBranches = show remote branches
gb.editUsers = edit users
gb.password = password
gb.confirmPassword = confirm password
gb.repositories = repositories
gb.canAdmin can admin
gb.notRestricted = open repository
gb.cloneRestricted = clone-restricted repository
gb.pushRestricted = push-restricted repository
gb.viewRestricted = view-restricted repository
src/com/gitblit/wicket/GitBlitWebSession.java
@@ -7,6 +7,8 @@
import org.apache.wicket.protocol.http.WebSession;
import org.apache.wicket.protocol.http.request.WebClientInfo;
import com.gitblit.wicket.models.User;
public final class GitBlitWebSession extends WebSession {
    private static final long serialVersionUID = 1L;
src/com/gitblit/wicket/LoginPage.java
@@ -18,6 +18,7 @@
import com.gitblit.Constants;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.wicket.models.User;
public class LoginPage extends WebPage {
src/com/gitblit/wicket/RepositoryPage.java
@@ -161,11 +161,17 @@
    protected RepositoryModel getRepositoryModel() {
        if (m == null) {
            m = GitBlit.self().getRepositoryModel(repositoryName);
            RepositoryModel model = GitBlit.self().getRepositoryModel(GitBlitWebSession.get().getUser(), repositoryName);
            if (model == null) {
                error("Unauthorized access for repository " + repositoryName);
                redirectToInterceptPage(new RepositoriesPage());
                return null;
            }
            m = model;
        }
        return m;
    }
    protected RevCommit getCommit() {
        RevCommit commit = JGitUtils.getCommit(r, objectId);
        if (commit == null) {
src/com/gitblit/wicket/User.java
File was deleted
src/com/gitblit/wicket/WicketUtils.java
@@ -227,6 +227,10 @@
    public static int getPage(PageParameters params) {
        return params.getInt("page", 1); // index from 1
    }
    public static String getUsername(PageParameters params) {
        return params.getString("user", "");
    }
    public static Label createDateLabel(String wicketId, Date date, TimeZone timeZone) {
        DateFormat df = new SimpleDateFormat(GitBlit.self().settings().getString(Keys.web.datestampShortFormat, "MM/dd/yy"));
src/com/gitblit/wicket/models/RepositoryModel.java
@@ -3,28 +3,29 @@
import java.io.Serializable;
import java.util.Date;
import com.gitblit.Constants.AccessRestrictionType;
public class RepositoryModel implements Serializable {
    private static final long serialVersionUID = 1L;
    public String name;
    public String description;
    public String owner;
    public String group;
    public Date lastChange;
    public boolean hasCommits;
    public boolean showRemoteBranches;
    public boolean useTickets;
    public boolean useDocs;
    public boolean useRestrictedAccess;
    public AccessRestrictionType accessRestriction;
    public RepositoryModel() {
    }
    public RepositoryModel(String name, String description, String owner, Date lastchange) {
        this.name = name;
        this.description = description;
        this.owner = owner;
        this.lastChange = lastchange;
    }
    }
}
src/com/gitblit/wicket/models/User.java
New file
@@ -0,0 +1,89 @@
package com.gitblit.wicket.models;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.gitblit.Constants.AccessRestrictionType;
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;
    private String password;
    private String cookie;
    private boolean canAdmin = false;
    private List<String> repositories = new ArrayList<String>();
    public User(String username) {
        this.username = username;
    }
    public String getUsername() {
        return username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public void canAdmin(boolean value) {
        canAdmin = value;
    }
    public boolean canAdmin() {
        return canAdmin;
    }
    public boolean canClone(RepositoryModel repository) {
        return canAccess(repository, AccessRestrictionType.CLONE);
    }
    public boolean canPush(RepositoryModel repository) {
        return canAccess(repository, AccessRestrictionType.PUSH);
    }
    public boolean canView(RepositoryModel repository) {
        return canAccess(repository, AccessRestrictionType.VIEW);
    }
    private boolean canAccess(RepositoryModel repository, AccessRestrictionType minimum) {
        if (repository.accessRestriction.atLeast(minimum)) {
            // repository is restricted, must check roles
            return canAdmin || repositories.contains(repository.name);
        } else {
            // repository is not restricted
            return true;
        }
    }
    public void setCookie(String cookie) {
        this.cookie = cookie;
    }
    public String getCookie() {
        return cookie;
    }
    public void setRepositories(List<String> repositories) {
        this.repositories.clear();
        this.repositories.addAll(repositories);
    }
    public void addRepository(String name) {
        repositories.add(name.toLowerCase());
    }
    public List<String> getRepositories() {
        return repositories;
    }
    public String toString() {
        return username;
    }
}
src/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -4,8 +4,8 @@
      xml:lang="en"  
      lang="en"> 
<body>
<wicket:extend>
<body onload="document.getElementById('name').focus();">
    <!-- Push content down to preserve header image -->
    <div style="padding-top:20px"></div>
@@ -15,18 +15,18 @@
    <form wicket:id="editForm">
        <table class="plain">
            <tbody>
                <tr><th><wicket:message key="gb.name"></wicket:message></th><td class="edit"><input type="text" wicket:id="name" size="30" tabindex="1" /></td></tr>
                <tr><th><wicket:message key="gb.description"></wicket:message></th><td class="edit"><input type="text" wicket:id="description" size="80" tabindex="2" /></td></tr>
                <tr><th><wicket:message key="gb.owner"></wicket:message></th><td class="edit"><input type="text" wicket:id="owner" size="30" tabindex="3" /></td></tr>
                <tr><th><wicket:message key="gb.group"></wicket:message></th><td class="edit"><input type="text" wicket:id="group" size="30" tabindex="4" /></td></tr>
                <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 repository Markdown documentation</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><td class="edit" colspan="2"><input type="submit" value="Save" wicket:message="value:gb.save" tabindex="8" /></td></tr>
                <tr><th></th><td class="editButton"><input type="submit" value="Save" wicket:message="value:gb.save" tabindex="8" /></td></tr>
            </tbody>
        </table>
    </form>    
</wicket:extend>
</body>
</wicket:extend>
</html>
src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -1,13 +1,16 @@
package com.gitblit.wicket.pages;
import java.util.Arrays;
import java.util.Date;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.CompoundPropertyModel;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.GitBlit;
import com.gitblit.GitBlitException;
import com.gitblit.wicket.AdminPage;
@@ -40,7 +43,7 @@
        if (isCreate) {
            super.setupPage("", getString("gb.newRepository"));
        } else {
            super.setupPage("", getString("gb.edit"));
            super.setupPage("", getString("gb.edit") + " " + repositoryModel.name);
        }
        CompoundPropertyModel<RepositoryModel> model = new CompoundPropertyModel<RepositoryModel>(repositoryModel);
        Form<RepositoryModel> form = new Form<RepositoryModel>("editForm", model) {
@@ -59,12 +62,12 @@
                setResponsePage(RepositoriesPage.class);
            }
        };
        // field names reflective match RepositoryModel fields
        form.add(new TextField<String>("name").setEnabled(isCreate));
        form.add(new TextField<String>("description"));
        form.add(new TextField<String>("owner"));
        form.add(new TextField<String>("group"));
        form.add(new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays.asList(AccessRestrictionType.values())));
        form.add(new CheckBox("useTickets"));
        form.add(new CheckBox("useDocs"));
        form.add(new CheckBox("showRemoteBranches"));
src/com/gitblit/wicket/pages/EditUserPage.html
New file
@@ -0,0 +1,29 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
      xml:lang="en"
      lang="en">
<wicket:extend>
<body onload="document.getElementById('username').focus();">
    <!-- Push content down to preserve header image -->
    <div style="padding-top:20px"></div>
    <div style="text-align:center;" wicket:id="feedback">[Feedback Panel]</div>
    <!-- Repository Table -->
    <form wicket:id="editForm">
        <table class="plain">
            <tbody>
                <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></th><td class="editButton"><input type="submit" value="Save" wicket:message="value:gb.save" tabindex="7" /></td></tr>
            </tbody>
        </table>
    </form>
</body>
</wicket:extend>
</html>
src/com/gitblit/wicket/pages/EditUserPage.java
New file
@@ -0,0 +1,97 @@
package com.gitblit.wicket.pages;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
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.Form;
import org.apache.wicket.markup.html.form.PasswordTextField;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.util.CollectionModel;
import org.apache.wicket.model.util.ListModel;
import org.eclipse.jetty.http.security.Credential.MD5;
import com.gitblit.GitBlit;
import com.gitblit.GitBlitException;
import com.gitblit.wicket.AdminPage;
import com.gitblit.wicket.BasePage;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.models.User;
@AdminPage
public class EditUserPage extends BasePage {
    private final boolean isCreate;
    public EditUserPage() {
        // create constructor
        super();
        isCreate = true;
        setupPage(new User(""));
    }
    public EditUserPage(PageParameters params) {
        // edit constructor
        super(params);
        isCreate = false;
        String name = WicketUtils.getUsername(params);
        User model = GitBlit.self().getUser(name);
        setupPage(model);
    }
    protected void setupPage(final User userModel) {
        if (isCreate) {
            super.setupPage("", getString("gb.newUser"));
        } else {
            super.setupPage("", getString("gb.edit"));
        }
        final Model<String> confirmPassword = new Model<String>();
        CompoundPropertyModel<User> model = new CompoundPropertyModel<User>(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);
        Form<User> form = new Form<User>("editForm", model) {
            private static final long serialVersionUID = 1L;
            @Override
            protected void onSubmit() {
                if (!userModel.getPassword().equals(confirmPassword.getObject())) {
                    error("Passwords do not match!");
                    return;
                }
                userModel.setPassword(MD5.digest(userModel.getPassword()));
                Iterator<String> selectedRepositories = repositories.getSelectedChoices();
                List<String> repos = new ArrayList<String>();
                while (selectedRepositories.hasNext()) {
                    repos.add(selectedRepositories.next());
                }
                userModel.setRepositories(repos);
                try {
                    GitBlit.self().editUserModel(userModel, isCreate);
                } catch (GitBlitException e) {
                    error(e.getMessage());
                    return;
                }
                setRedirect(true);
                setResponsePage(EditUserPage.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));
        form.add(new CheckBox("canAdmin"));
        form.add(repositories);
        add(form);
    }
}
src/com/gitblit/wicket/pages/RepositoriesPage.html
@@ -41,7 +41,7 @@
    <wicket:fragment wicket:id="adminLinks">
        <!-- page nav links -->    
        <div class="page_nav">
            <a wicket:id="newRepository"><wicket:message key="gb.newRepository"></wicket:message></a> | <a wicket:id="newUser"><wicket:message key="gb.newUser"></wicket:message></a>
            <a wicket:id="newRepository"><wicket:message key="gb.newRepository"></wicket:message></a> | <a wicket:id="newUser"><wicket:message key="gb.newUser"></wicket:message></a> | <a wicket:id="editUsers"><wicket:message key="gb.editUsers"></wicket:message></a>
        </div>    
    </wicket:fragment>
    
src/com/gitblit/wicket/pages/RepositoriesPage.java
@@ -33,6 +33,7 @@
import com.gitblit.wicket.LinkPanel;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.models.RepositoryModel;
import com.gitblit.wicket.models.User;
public class RepositoriesPage extends BasePage {
@@ -50,7 +51,8 @@
        Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
        adminLinks.add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
        adminLinks.add(new BookmarkablePageLink<Void>("newUser", RepositoriesPage.class));
        adminLinks.add(new BookmarkablePageLink<Void>("newUser", EditUserPage.class));
        adminLinks.add(new BookmarkablePageLink<Void>("editUsers", RepositoriesPage.class));
        add(adminLinks.setVisible(showAdmin));
        // display an error message cached from a redirect
@@ -59,7 +61,7 @@
            error(cachedMessage);
            System.out.println("displayed message");
        }
        // Load the markdown welcome message
        String messageSource = GitBlit.self().settings().getString(Keys.web.repositoriesMessage, "gitblit");
        String message = "";
@@ -97,7 +99,8 @@
        }
        add(repositoriesMessage);
        List<RepositoryModel> rows = GitBlit.self().getRepositoryModels();
        User user = GitBlitWebSession.get().getUser();
        List<RepositoryModel> rows = GitBlit.self().getRepositoryModels(user);
        DataProvider dp = new DataProvider(rows);
        DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("repository", dp) {
            private static final long serialVersionUID = 1L;
@@ -113,27 +116,38 @@
                } else {
                    // New repository
                    item.add(new Label("repositoryName", entry.name + "<span class='empty'>(empty)</span>").setEscapeModelStrings(false));
                    item.add(new Label("repositoryDescription", entry.description));
                    item.add(new Label("repositoryDescription", entry.description));
                }
                if (entry.useTickets) {
                    item.add(WicketUtils.newImage("ticketsIcon", "bug_16x16.png", getString("gb.tickets")));
                } else {
                    item.add(WicketUtils.newClearPixel("ticketsIcon"));
                    item.add(WicketUtils.newBlankImage("ticketsIcon"));
                }
                if (entry.useDocs) {
                    item.add(WicketUtils.newImage("docsIcon", "book_16x16.png", getString("gb.docs")));
                } else {
                    item.add(WicketUtils.newClearPixel("docsIcon"));
                    item.add(WicketUtils.newBlankImage("docsIcon"));
                }
                if (entry.useRestrictedAccess) {
                    item.add(WicketUtils.newImage("restrictedAccessIcon", "lock_16x16.png", getString("gb.restrictedAccess")));
                } else {
                    item.add(WicketUtils.newClearPixel("restrictedAccessIcon"));
                switch (entry.accessRestriction) {
                case NONE:
                    item.add(WicketUtils.newBlankImage("restrictedAccessIcon"));
                    break;
                case PUSH:
                    item.add(WicketUtils.newImage("restrictedAccessIcon", "lock_go_16x16.png", getString("gb.pushRestricted")));
                    break;
                case CLONE:
                    item.add(WicketUtils.newImage("restrictedAccessIcon", "lock_pull_16x16.png", getString("gb.cloneRestricted")));
                    break;
                case VIEW:
                    item.add(WicketUtils.newImage("restrictedAccessIcon", "shield_16x16.png", getString("gb.viewRestricted")));
                    break;
                default:
                    item.add(WicketUtils.newBlankImage("restrictedAccessIcon"));
                }
                item.add(new Label("repositoryOwner", entry.owner));
                String lastChange = TimeUtils.timeAgo(entry.lastChange);
src/com/gitblit/wicket/resources/gitblit.css
@@ -444,8 +444,13 @@
    padding: 8px;
}
table.plain td.edit {
table.plain td.edit {
    padding: 3px;
}
table.plain td.editButton {
    padding:0px;
    padding-top: 10px;
}
table.plain td.edit input {
@@ -517,6 +522,19 @@
    color: black;
    font-weight: bold;
}
table.palette { border:0;}
table.palette td.header {
    font-weight: bold;
    background-color: #D2C3AF !important;
    padding: 3px !important;
    border: 1px solid #808080 !important;
    border-bottom: 0px solid !important;
    border-radius: 3px 3px 0 0;
}
table.palette td.pane {
    padding: 0px;
}
tr th a { padding-right: 15px; background-position: right; background-repeat:no-repeat; }
tr th.wicket_orderDown a {background-image: url(arrow_down.png); }
@@ -619,22 +637,22 @@
span .otherRef {
    background-color: #ffaaff;
    border-color: #ffccff #ff00ee #ff00ee #ffccff;
    border-color: #ff00ee;
}
span .remoteRef {
    background-color: #cAc2f5;
    border-color: #ccccff #0033cc #0033cc #ccccff;
    border-color: #6c6cbf;
}
span .tagRef {
    background-color: #ffffaa;
    border-color: #ffcc00 #ffcc00 #ffcc00 #ffcc00;
    border-color: #ffcc00;
}
span .headRef {
    background-color: #ccffcc;
    border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
    border-color: #00cc33;
}
.feedbackPanelERROR {    
src/com/gitblit/wicket/resources/lock_go_16x16.png
src/com/gitblit/wicket/resources/lock_pull_16x16.png
src/com/gitblit/wicket/resources/shield_16x16.png
users.properties
@@ -1,2 +1,3 @@
test: test,pull
admin: admin,pull,push,admin
#Wed May 11 21:30:28 EDT 2011
admin=admin,\#admin
test=test