James Moger
2012-10-19 822dfe5d6f1b97b7849bc6fd735ee8c9d1103c19
Completed permissions UI, RPC, and Manager support (issue-36)
2 files renamed
7 files deleted
3 files added
25 files modified
1339 ■■■■ changed files
docs/02_rpc.mkd 18 ●●●● patch | view | raw | blame | history
docs/04_releases.mkd 11 ●●●● patch | view | raw | blame | history
src/com/gitblit/ConfigUserService.java 12 ●●●● patch | view | raw | blame | history
src/com/gitblit/Constants.java 5 ●●●●● patch | view | raw | blame | history
src/com/gitblit/FederationPullExecutor.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/FileUserService.java 3 ●●●● patch | view | raw | blame | history
src/com/gitblit/GitBlit.java 43 ●●●● patch | view | raw | blame | history
src/com/gitblit/RpcServlet.java 35 ●●●●● patch | view | raw | blame | history
src/com/gitblit/client/EditRepositoryDialog.java 29 ●●●● patch | view | raw | blame | history
src/com/gitblit/client/EditTeamDialog.java 23 ●●●●● patch | view | raw | blame | history
src/com/gitblit/client/EditUserDialog.java 22 ●●●●● patch | view | raw | blame | history
src/com/gitblit/client/GitblitClient.java 51 ●●●●● patch | view | raw | blame | history
src/com/gitblit/client/RegistrantPermissionsPanel.java 140 ●●●●● patch | view | raw | blame | history
src/com/gitblit/client/RegistrantPermissionsTableModel.java 120 ●●●●● patch | view | raw | blame | history
src/com/gitblit/client/RepositoriesPanel.java 21 ●●●●● patch | view | raw | blame | history
src/com/gitblit/client/TeamsPanel.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/client/UsersPanel.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/client/UsersTableModel.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/models/RegistrantAccessPermission.java 60 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/RepositoryAccessPermission.java 52 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/TeamAccessPermission.java 51 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/TeamModel.java 7 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/UserAccessPermission.java 51 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/UserModel.java 11 ●●●● patch | view | raw | blame | history
src/com/gitblit/utils/RpcUtils.java 66 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/GitBlitWebApp.properties 3 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditRepositoryPage.java 16 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditTeamPage.java 12 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditUserPage.java 14 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java 56 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/TeamPermissionsPanel.html 24 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/TeamPermissionsPanel.java 157 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/UserPermissionsPanel.html 24 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/UserPermissionsPanel.java 157 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/UsersPanel.java 2 ●●● patch | view | raw | blame | history
tests/com/gitblit/tests/RpcTests.java 27 ●●●●● patch | view | raw | blame | history
docs/02_rpc.mkd
@@ -64,9 +64,17 @@
<tr><td>Gitblit v0.7.0</td><td>1 (inferred version)</td></tr>
<tr><td>Gitblit v0.8.0</td><td>2</td></tr>
<tr><td>Gitblit v0.9.0 - v1.0.0</td><td>3</td></tr>
<tr><td>Gitblit v1.1.0+</td><td>4</td></tr>
<tr><td>Gitblit v1.1.0</td><td>4</td></tr>
<tr><td>Gitblit v1.2.0+</td><td>5</td></tr>
</tbody>
</table>
#### Protocol Version 5
- *SET_REPOSITORY_MEMBERS* will reject all calls because this would elevate all discrete permissions to RW+
Use *SET_REPOSITORY_MEMBER_PERMISSIONS* instead.
- *SET_REPOSITORY_TEAMS* will reject all calls because this would elevate all discrete permissions to RW+
Use *SET_REPOSITORY_TEAM_PERMISSIONS* instead.
### RPC Request and Response Types
<table class="table">
@@ -90,9 +98,13 @@
<tr><td>EDIT_TEAM</td><td>team name</td><td><em>admin</em></td><td>2</td><td>TeamModel</td><td>-</td></tr>
<tr><td>DELETE_TEAM</td><td>team name</td><td><em>admin</em></td><td>2</td><td>-</td><td>-</td></tr>
<tr><td>LIST_REPOSITORY_MEMBERS</td><td>repository name</td><td><em>admin</em></td><td>1</td><td>-</td><td>List&lt;String&gt;</td></tr>
<tr><td>SET_REPOSITORY_MEMBERS</td><td>repository name</td><td><em>admin</em></td><td>1</td><td>List&lt;String&gt;</td><td>-</td></tr>
<tr><td><s>SET_REPOSITORY_MEMBERS</s></td><td><s>repository name</s></td><td><em><s>admin</s></em></td><td><s>1</s></td><td><s>List&lt;String&gt;</s></td><td>-</td></tr>
<tr><td>LIST_REPOSITORY_MEMBER_PERMISSIONS</td><td>repository name</td><td><em>admin</em></td><td>5</td><td>-</td><td>List&lt;String&gt;</td></tr>
<tr><td>SET_REPOSITORY_MEMBER_PERMISSIONS</td><td>repository name</td><td><em>admin</em></td><td>5</td><td>List&lt;String&gt;</td><td>-</td></tr>
<tr><td>LIST_REPOSITORY_TEAMS</td><td>repository name</td><td><em>admin</em></td><td>2</td><td>-</td><td>List&lt;String&gt;</td></tr>
<tr><td>SET_REPOSITORY_TEAMS</td><td>repository name</td><td><em>admin</em></td><td>2</td><td>List&lt;String&gt;</td><td>-</td></tr>
<tr><td><s>SET_REPOSITORY_TEAMS</s></td><td><s>repository name</s></td><td><em><s>admin</s></em></td><td><s>2</s></td><td><s>List&lt;String&gt;</s></td><td>-</td></tr>
<tr><td>LIST_REPOSITORY_TEAM_PERMISSIONS</td><td>repository name</td><td><em>admin</em></td><td>5</td><td>-</td><td>List&lt;String&gt;</td></tr>
<tr><td>SET_REPOSITORY_TEAM_PERMISSIONS</td><td>repository name</td><td><em>admin</em></td><td>5</td><td>List&lt;String&gt;</td><td>-</td></tr>
<tr><td>LIST_SETTINGS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>ServerSettings (management keys)</td></tr>
<tr><td>CLEAR_REPOSITORY_CACHE</td><td>-</td><td><em>-</em></td><td>4</td><td>-</td><td>-</td></tr>
<tr><td colspan='6'><em>web.enableRpcAdministration=true</em></td></tr>
docs/04_releases.mkd
@@ -1,8 +1,10 @@
## Release History
<div class="alert alert-error">
<div class="alert alert-info">
<h4>Update Note</h4>
If you are updating from an earlier release AND you have indexed branches with the Lucene indexing feature, you need to be aware that this release will completely re-index your repositories.  Please be sure to provide ample heap resources as appropriate for your installation.
The permissions model has changed in this release.
<p>
If you are updating your server, you must also update any Gitblit Manager and Federation Client installs to 1.2.0 as well.  The data model used by the RPC mechanism has changed slightly for the new permissions infrastructure.
</div>
### Current Release
@@ -65,6 +67,11 @@
### Older Releases
<div class="alert alert-error">
<h4>Update Note</h4>
If you are updating from an earlier release AND you have indexed branches with the Lucene indexing feature, you need to be aware that this release will completely re-index your repositories.  Please be sure to provide ample heap resources as appropriate for your installation.
</div>
**1.1.0** *released 2012-08-25*
#### fixes
src/com/gitblit/ConfigUserService.java
@@ -840,16 +840,8 @@
            }
            config.setStringList(USER, model.username, ROLE, roles);
            // repository memberships
            if (model.permissions == null) {
                // null check on "final" repositories because JSON-sourced UserModel
                // can have a null repositories object
                if (!ArrayUtils.isEmpty(model.repositories)) {
                    config.setStringList(USER, model.username, REPOSITORY, new ArrayList<String>(
                            model.repositories));
                }
            } else {
                // discrete repository permissions
            // discrete repository permissions
            if (model.permissions != null) {
                List<String> permissions = new ArrayList<String>();
                for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
                    if (entry.getValue().exceeds(AccessPermission.NONE)) {
src/com/gitblit/Constants.java
@@ -255,6 +255,7 @@
        LIST_USERS, CREATE_USER, EDIT_USER, DELETE_USER, 
        LIST_TEAMS, CREATE_TEAM, EDIT_TEAM, DELETE_TEAM,
        LIST_REPOSITORY_MEMBERS, SET_REPOSITORY_MEMBERS, LIST_REPOSITORY_TEAMS, SET_REPOSITORY_TEAMS, 
        LIST_REPOSITORY_MEMBER_PERMISSIONS, SET_REPOSITORY_MEMBER_PERMISSIONS, LIST_REPOSITORY_TEAM_PERMISSIONS, SET_REPOSITORY_TEAM_PERMISSIONS,
        LIST_FEDERATION_REGISTRATIONS, LIST_FEDERATION_RESULTS, LIST_FEDERATION_PROPOSALS, LIST_FEDERATION_SETS,
        EDIT_SETTINGS, LIST_STATUS;
@@ -379,6 +380,10 @@
        }
    }
    
    public static enum RegistrantType {
        REPOSITORY, USER, TEAM;
    }
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Unused {
src/com/gitblit/FederationPullExecutor.java
@@ -335,7 +335,7 @@
                        // reparent all repository permissions if the local
                        // repositories are stored within subfolders
                        if (!StringUtils.isEmpty(registrationFolder)) {
                            if (user.permissions != null && user.permissions.size() > 0) {
                            if (user.permissions != null) {
                                // pulling from >= 1.2 version
                                Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
                                user.permissions.clear();
@@ -359,7 +359,7 @@
                            GitBlit.self().updateUserModel(user.username, user, true);
                        } else {
                            // update repository permissions of local user
                            if (user.permissions != null && user.permissions.size() > 0) {
                            if (user.permissions != null) {
                                // pulling from >= 1.2 version
                                Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
                                for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) {
src/com/gitblit/FileUserService.java
@@ -329,8 +329,7 @@
            UserModel oldUser = getUserModel(username);
            List<String> roles;
            if (model.permissions == null) {
                // legacy, use repository list
                roles = new ArrayList<String>(model.repositories);
                roles = new ArrayList<String>();
            } else {
                // discrete repository permissions
                roles = new ArrayList<String>();
src/com/gitblit/GitBlit.java
@@ -78,20 +78,20 @@
import com.gitblit.Constants.FederationRequest;
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.Constants.FederationToken;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
import com.gitblit.models.ForkModel;
import com.gitblit.models.Metric;
import com.gitblit.models.ProjectModel;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.SearchResult;
import com.gitblit.models.ServerSettings;
import com.gitblit.models.ServerStatus;
import com.gitblit.models.SettingModel;
import com.gitblit.models.TeamAccessPermission;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserAccessPermission;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.ByteFormat;
@@ -637,11 +637,11 @@
     * @param repository
     * @return a list of User-AccessPermission tuples
     */
    public List<UserAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
        List<UserAccessPermission> permissions = new ArrayList<UserAccessPermission>();
    public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
        List<RegistrantAccessPermission> permissions = new ArrayList<RegistrantAccessPermission>();
        for (String user : userService.getUsernamesForRepositoryRole(repository.name)) {
            AccessPermission ap = userService.getUserModel(user).getRepositoryPermission(repository);
            permissions.add(new UserAccessPermission(user, ap));
            permissions.add(new RegistrantAccessPermission(user, ap, RegistrantType.USER));
        }
        return permissions;
    }
@@ -653,10 +653,10 @@
     * @param permissions
     * @return true if the user models have been updated
     */
    public boolean setUserAccessPermissions(RepositoryModel repository, List<UserAccessPermission> permissions) {
    public boolean setUserAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
        List<UserModel> users = new ArrayList<UserModel>();
        for (UserAccessPermission up : permissions) {
            UserModel user = userService.getUserModel(up.user);
        for (RegistrantAccessPermission up : permissions) {
            UserModel user = userService.getUserModel(up.registrant);
            user.setRepositoryPermission(repository.name, up.permission);
            users.add(user);
        }
@@ -686,7 +686,9 @@
     */
    @Deprecated
    public boolean setRepositoryUsers(RepositoryModel repository, List<String> repositoryUsers) {
        return userService.setUsernamesForRepositoryRole(repository.name, repositoryUsers);
        // rejects all changes since 1.2.0 because this would elevate
        // all discrete access permissions to RW+
        return false;
    }
    /**
@@ -767,11 +769,11 @@
     * @param repository
     * @return a list of Team-AccessPermission tuples
     */
    public List<TeamAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
        List<TeamAccessPermission> permissions = new ArrayList<TeamAccessPermission>();
    public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
        List<RegistrantAccessPermission> permissions = new ArrayList<RegistrantAccessPermission>();
        for (String team : userService.getTeamnamesForRepositoryRole(repository.name)) {
            AccessPermission ap = userService.getTeamModel(team).getRepositoryPermission(repository);
            permissions.add(new TeamAccessPermission(team, ap));
            permissions.add(new RegistrantAccessPermission(team, ap, RegistrantType.TEAM));
        }
        return permissions;
    }
@@ -783,10 +785,10 @@
     * @param permissions
     * @return true if the team models have been updated
     */
    public boolean setTeamAccessPermissions(RepositoryModel repository, List<TeamAccessPermission> permissions) {
    public boolean setTeamAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
        List<TeamModel> teams = new ArrayList<TeamModel>();
        for (TeamAccessPermission tp : permissions) {
            TeamModel team = userService.getTeamModel(tp.team);
        for (RegistrantAccessPermission tp : permissions) {
            TeamModel team = userService.getTeamModel(tp.registrant);
            team.setRepositoryPermission(repository.name, tp.permission);
            teams.add(team);
        }
@@ -794,14 +796,13 @@
    }
    
    /**
     * Returns the list of all teams who are allowed to bypass the access
     * restriction placed on the specified repository.
     * Returns the list of all teams who have an explicit access permission for
     * the specified repository.
     * 
     * @see IUserService.getTeamnamesForRepositoryRole(String)
     * @param repository
     * @return list of all teamnames that can bypass the access restriction
     * @return list of all teamnames with explicit access permissions to the repository
     */
    @Deprecated
    public List<String> getRepositoryTeams(RepositoryModel repository) {
        return userService.getTeamnamesForRepositoryRole(repository.name);
    }
@@ -817,7 +818,9 @@
     */
    @Deprecated
    public boolean setRepositoryTeams(RepositoryModel repository, List<String> repositoryTeams) {
        return userService.setTeamnamesForRepositoryRole(repository.name, repositoryTeams);
        // rejects all changes since 1.2.0 because this would elevate
        // all discrete access permissions to RW+
        return false;
    }
    /**
src/com/gitblit/RpcServlet.java
@@ -30,6 +30,7 @@
import org.eclipse.jgit.lib.Repository;
import com.gitblit.Constants.RpcRequest;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RefModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.ServerSettings;
@@ -49,7 +50,7 @@
    private static final long serialVersionUID = 1L;
    public static final int PROTOCOL_VERSION = 4;
    public static final int PROTOCOL_VERSION = 5;
    public RpcServlet() {
        super();
@@ -226,25 +227,33 @@
            RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
            result = GitBlit.self().getRepositoryUsers(model);
        } else if (RpcRequest.SET_REPOSITORY_MEMBERS.equals(reqType)) {
            // update repository access list
            // rejected since 1.2.0
            response.setStatus(failureCode);
        } else if (RpcRequest.LIST_REPOSITORY_MEMBER_PERMISSIONS.equals(reqType)) {
            // get repository member permissions
            RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
            Collection<String> names = deserialize(request, response, RpcUtils.NAMES_TYPE);
            List<String> users = new ArrayList<String>(names);
            if (!GitBlit.self().setRepositoryUsers(model, users)) {
                response.setStatus(failureCode);
            }
            result = GitBlit.self().getUserAccessPermissions(model);
        } else if (RpcRequest.SET_REPOSITORY_MEMBER_PERMISSIONS.equals(reqType)) {
            // set the repository permissions for the specified users
            RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
            Collection<RegistrantAccessPermission> permissions = deserialize(request, response, RpcUtils.REGISTRANT_PERMISSIONS_TYPE);
            result = GitBlit.self().setUserAccessPermissions(model, permissions);
        } else if (RpcRequest.LIST_REPOSITORY_TEAMS.equals(reqType)) {
            // get repository teams
            RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
            result = GitBlit.self().getRepositoryTeams(model);
        } else if (RpcRequest.SET_REPOSITORY_TEAMS.equals(reqType)) {
            // update repository team access list
            // rejected since 1.2.0
            response.setStatus(failureCode);
        } else if (RpcRequest.LIST_REPOSITORY_TEAM_PERMISSIONS.equals(reqType)) {
            // get repository team permissions
            RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
            Collection<String> names = deserialize(request, response, RpcUtils.NAMES_TYPE);
            List<String> teams = new ArrayList<String>(names);
            if (!GitBlit.self().setRepositoryTeams(model, teams)) {
                response.setStatus(failureCode);
            }
            result = GitBlit.self().getTeamAccessPermissions(model);
        } else if (RpcRequest.SET_REPOSITORY_TEAM_PERMISSIONS.equals(reqType)) {
            // set the repository permissions for the specified teams
            RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
            Collection<RegistrantAccessPermission> permissions = deserialize(request, response, RpcUtils.REGISTRANT_PERMISSIONS_TYPE);
            result = GitBlit.self().setTeamAccessPermissions(model, permissions);
        } else if (RpcRequest.LIST_FEDERATION_REGISTRATIONS.equals(reqType)) {
            // return the list of federation registrations
            if (allowAdmin) {
src/com/gitblit/client/EditRepositoryDialog.java
@@ -59,6 +59,7 @@
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
@@ -116,11 +117,11 @@
    private JComboBox headRefField;
    private JPalette<String> usersPalette;
    private RegistrantPermissionsPanel usersPalette;
    private JPalette<String> setsPalette;
    private JPalette<String> teamsPalette;
    private RegistrantPermissionsPanel teamsPalette;
    
    private JPalette<String> indexedBranchesPalette;
@@ -280,7 +281,7 @@
        clonePushPanel
        .add(newFieldPanel(Translation.get("gb.verifyCommitter"), verifyCommitter));
        usersPalette = new JPalette<String>();
        usersPalette = new RegistrantPermissionsPanel();
        JPanel northAccessPanel = new JPanel(new BorderLayout(5, 5));
        northAccessPanel.add(newFieldPanel(Translation.get("gb.accessRestriction"),
                accessRestriction), BorderLayout.NORTH);
@@ -290,13 +291,13 @@
        JPanel accessPanel = new JPanel(new BorderLayout(5, 5));
        accessPanel.add(northAccessPanel, BorderLayout.NORTH);
        accessPanel.add(newFieldPanel(Translation.get("gb.permittedUsers"),
        accessPanel.add(newFieldPanel(Translation.get("gb.userPermissions"),
                        usersPalette), BorderLayout.CENTER);
        teamsPalette = new JPalette<String>();
        teamsPalette = new RegistrantPermissionsPanel();
        JPanel teamsPanel = new JPanel(new BorderLayout(5, 5));
        teamsPanel.add(
                newFieldPanel(Translation.get("gb.permittedTeams"),
                newFieldPanel(Translation.get("gb.teamPermissions"),
                        teamsPalette), BorderLayout.CENTER);
        setsPalette = new JPalette<String>();
@@ -545,16 +546,16 @@
        this.allowNamed.setSelected(!authenticated);
    }
    public void setUsers(String owner, List<String> all, List<String> selected) {
    public void setUsers(String owner, List<String> all, List<RegistrantAccessPermission> permissions) {
        ownerField.setModel(new DefaultComboBoxModel(all.toArray()));
        if (!StringUtils.isEmpty(owner)) {
            ownerField.setSelectedItem(owner);
        }
        usersPalette.setObjects(all, selected);
        usersPalette.setObjects(all, permissions);
    }
    public void setTeams(List<String> all, List<String> selected) {
        teamsPalette.setObjects(all, selected);
    public void setTeams(List<String> all, List<RegistrantAccessPermission> permissions) {
        teamsPalette.setObjects(all, permissions);
    }
    public void setRepositories(List<RepositoryModel> repositories) {
@@ -607,12 +608,12 @@
        return repository;
    }
    public List<String> getPermittedUsers() {
        return usersPalette.getSelections();
    public List<RegistrantAccessPermission> getUserAccessPermissions() {
        return usersPalette.getPermissions();
    }
    public List<String> getPermittedTeams() {
        return teamsPalette.getSelections();
    public List<RegistrantAccessPermission> getTeamAccessPermissions() {
        return teamsPalette.getPermissions();
    }
    
    public void setCustomFields(RepositoryModel repository, Map<String, String> customFields) {
src/com/gitblit/client/EditTeamDialog.java
@@ -45,6 +45,7 @@
import javax.swing.KeyStroke;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.ServerSettings;
import com.gitblit.models.TeamModel;
@@ -74,7 +75,7 @@
    private JTextField mailingListsField;
    private JPalette<String> repositoryPalette;
    private RegistrantPermissionsPanel repositoryPalette;
    private JPalette<String> userPalette;
@@ -138,7 +139,7 @@
        fieldsPanel.add(newFieldPanel(Translation.get("gb.mailingLists"), mailingListsField));
        final Insets _insets = new Insets(5, 5, 5, 5);
        repositoryPalette = new JPalette<String>();
        repositoryPalette = new RegistrantPermissionsPanel();
        userPalette = new JPalette<String>();
        userPalette.setEnabled(settings.supportsTeamMembershipChanges);
        
@@ -278,8 +279,9 @@
            team.mailingLists.addAll(list);
        }
        team.repositories.clear();
        team.repositories.addAll(repositoryPalette.getSelections());
        for (RegistrantAccessPermission rp : repositoryPalette.getPermissions()) {
            team.setRepositoryPermission(rp.registrant, rp.permission);
        }
        team.users.clear();
        team.users.addAll(userPalette.getSelections());
@@ -305,18 +307,21 @@
        }
    }
    public void setRepositories(List<RepositoryModel> repositories, List<String> selected) {
    public void setRepositories(List<RepositoryModel> repositories, List<RegistrantAccessPermission> permissions) {
        List<String> restricted = new ArrayList<String>();
        for (RepositoryModel repo : repositories) {
            if (repo.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
                restricted.add(repo.name);
            }
        }
        StringUtils.sortRepositorynames(restricted);
        if (selected != null) {
            StringUtils.sortRepositorynames(selected);
        // remove repositories for which team already has a permission
        for (RegistrantAccessPermission rp : permissions) {
            restricted.remove(rp.registrant);
        }
        repositoryPalette.setObjects(restricted, selected);
        StringUtils.sortRepositorynames(restricted);
        repositoryPalette.setObjects(restricted, permissions);
    }
    public void setUsers(List<String> users, List<String> selected) {
src/com/gitblit/client/EditUserDialog.java
@@ -47,6 +47,7 @@
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Keys;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.ServerSettings;
import com.gitblit.models.TeamModel;
@@ -85,7 +86,7 @@
    private JCheckBox notFederatedCheckbox;
    private JPalette<String> repositoryPalette;
    private RegistrantPermissionsPanel repositoryPalette;
    private JPalette<TeamModel> teamsPalette;
@@ -157,7 +158,7 @@
                notFederatedCheckbox));
        final Insets _insets = new Insets(5, 5, 5, 5);
        repositoryPalette = new JPalette<String>();
        repositoryPalette = new RegistrantPermissionsPanel();
        teamsPalette = new JPalette<TeamModel>();
        teamsPalette.setEnabled(settings.supportsTeamMembershipChanges);
@@ -318,8 +319,9 @@
        user.canCreate = canCreateCheckbox.isSelected();
        user.excludeFromFederation = notFederatedCheckbox.isSelected();
        user.repositories.clear();
        user.repositories.addAll(repositoryPalette.getSelections());
        for (RegistrantAccessPermission rp : repositoryPalette.getPermissions()) {
            user.setRepositoryPermission(rp.registrant, rp.permission);
        }
        user.teams.clear();
        user.teams.addAll(teamsPalette.getSelections());
@@ -338,18 +340,20 @@
        }
    }
    public void setRepositories(List<RepositoryModel> repositories, List<String> selected) {
    public void setRepositories(List<RepositoryModel> repositories, List<RegistrantAccessPermission> permissions) {
        List<String> restricted = new ArrayList<String>();
        for (RepositoryModel repo : repositories) {
            if (repo.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
                restricted.add(repo.name);
            }
        }
        StringUtils.sortRepositorynames(restricted);
        if (selected != null) {
            StringUtils.sortRepositorynames(selected);
        // remove repositories for which user already has a permission
        for (RegistrantAccessPermission rp : permissions) {
            restricted.remove(rp.registrant);
        }
        repositoryPalette.setObjects(restricted, selected);
        StringUtils.sortRepositorynames(restricted);
        repositoryPalette.setObjects(restricted, permissions);
    }
    public void setTeams(List<TeamModel> teams, List<TeamModel> selected) {
src/com/gitblit/client/GitblitClient.java
@@ -35,6 +35,7 @@
import com.gitblit.GitBlitException.UnauthorizedException;
import com.gitblit.GitBlitException.UnknownRequestException;
import com.gitblit.Keys;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FeedEntryModel;
import com.gitblit.models.FeedModel;
@@ -485,11 +486,19 @@
    public List<String> getPermittedUsernames(RepositoryModel repository) {
        List<String> usernames = new ArrayList<String>();
        for (UserModel user : this.allUsers) {
            if (user.repositories.contains(repository.name)) {
            if (user.hasRepositoryPermission(repository.name)) {
                usernames.add(user.username);
            }
        }
        return usernames;
    }
    public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) throws IOException {
        return RpcUtils.getRepositoryMemberPermissions(repository, url, account, password);
    }
    public boolean setUserAccessPermissions(RepositoryModel repository, List<RegistrantAccessPermission> permissions) throws IOException {
        return RpcUtils.setRepositoryMemberPermissions(repository, permissions, url, account, password);
    }
    public List<TeamModel> getTeams() {
@@ -508,11 +517,19 @@
    public List<String> getPermittedTeamnames(RepositoryModel repository) {
        List<String> teamnames = new ArrayList<String>();
        for (TeamModel team : this.allTeams) {
            if (team.repositories.contains(repository.name)) {
            if (team.hasRepositoryPermission(repository.name)) {
                teamnames.add(team.name);
            }
        }
        return teamnames;
    }
    public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) throws IOException {
        return RpcUtils.getRepositoryTeamPermissions(repository, url, account, password);
    }
    public boolean setTeamAccessPermissions(RepositoryModel repository, List<RegistrantAccessPermission> permissions) throws IOException {
        return RpcUtils.setRepositoryTeamPermissions(repository, permissions, url, account, password);
    }
    public TeamModel getTeamModel(String name) {
@@ -532,44 +549,44 @@
        return allRepositories;
    }
    public boolean createRepository(RepositoryModel repository, List<String> permittedUsers)
    public boolean createRepository(RepositoryModel repository, List<RegistrantAccessPermission> userPermissions)
            throws IOException {
        return createRepository(repository, permittedUsers, null);
        return createRepository(repository, userPermissions, null);
    }
    public boolean createRepository(RepositoryModel repository, List<String> permittedUsers,
            List<String> permittedTeams) throws IOException {
    public boolean createRepository(RepositoryModel repository, List<RegistrantAccessPermission> userPermissions,
            List<RegistrantAccessPermission> teamPermissions) throws IOException {
        boolean success = true;
        success &= RpcUtils.createRepository(repository, url, account, password);
        if (permittedUsers != null && permittedUsers.size() > 0) {
        if (userPermissions != null && userPermissions.size() > 0) {
            // if new repository has named members, set them
            success &= RpcUtils.setRepositoryMembers(repository, permittedUsers, url, account,
            success &= RpcUtils.setRepositoryMemberPermissions(repository, userPermissions, url, account,
                    password);
        }
        if (permittedTeams != null && permittedTeams.size() > 0) {
        if (teamPermissions != null && teamPermissions.size() > 0) {
            // if new repository has named teams, set them
            success &= RpcUtils.setRepositoryTeams(repository, permittedTeams, url, account,
            success &= RpcUtils.setRepositoryTeamPermissions(repository, teamPermissions, url, account,
                    password);
        }
        return success;
    }
    public boolean updateRepository(String name, RepositoryModel repository,
            List<String> permittedUsers) throws IOException {
        return updateRepository(name, repository, permittedUsers, null);
            List<RegistrantAccessPermission> userPermissions) throws IOException {
        return updateRepository(name, repository, userPermissions, null);
    }
    public boolean updateRepository(String name, RepositoryModel repository,
            List<String> permittedUsers, List<String> permittedTeams) throws IOException {
            List<RegistrantAccessPermission> userPermissions,    List<RegistrantAccessPermission> teamPermissions) throws IOException {
        boolean success = true;
        success &= RpcUtils.updateRepository(name, repository, url, account, password);
        // set the repository members
        if (permittedUsers != null) {
            success &= RpcUtils.setRepositoryMembers(repository, permittedUsers, url, account,
        if (userPermissions != null) {
            success &= RpcUtils.setRepositoryMemberPermissions(repository, userPermissions, url, account,
                    password);
        }
        if (permittedTeams != null) {
            success &= RpcUtils.setRepositoryTeams(repository, permittedTeams, url, account,
        if (teamPermissions != null) {
            success &= RpcUtils.setRepositoryTeamPermissions(repository, teamPermissions, url, account,
                    password);
        }
        return success;
src/com/gitblit/client/RegistrantPermissionsPanel.java
New file
@@ -0,0 +1,140 @@
/*
 * Copyright 2012 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.client;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.models.RegistrantAccessPermission;
public class RegistrantPermissionsPanel extends JPanel {
    private static final long serialVersionUID = 1L;
    private JTable permissionsTable;
    private RegistrantPermissionsTableModel tableModel;
    private DefaultComboBoxModel registrantModel;
    private JComboBox registrantSelector;
    private JComboBox permissionSelector;
    private JButton addButton;
    private JPanel addPanel;
    public RegistrantPermissionsPanel() {
        super(new BorderLayout(5, 5));
        tableModel = new RegistrantPermissionsTableModel();
        permissionsTable = new JTable(tableModel);
        permissionsTable.setPreferredScrollableViewportSize(new Dimension(400, 150));
        JScrollPane jsp = new JScrollPane(permissionsTable);
        add(jsp, BorderLayout.CENTER);
        permissionsTable.getColumnModel().getColumn(RegistrantPermissionsTableModel.Columns.Permission.ordinal())
                .setCellEditor(new AccessPermissionEditor());
        registrantModel = new DefaultComboBoxModel();
        registrantSelector = new JComboBox(registrantModel);
        permissionSelector = new JComboBox(AccessPermission.NEWPERMISSIONS);
        addButton = new JButton(Translation.get("gb.add"));
        addButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (registrantSelector.getSelectedIndex() < 0) {
                    return;
                }
                if (permissionSelector.getSelectedIndex() < 0) {
                    return;
                }
                RegistrantAccessPermission rp = new RegistrantAccessPermission();
                rp.registrant = registrantSelector.getSelectedItem().toString();
                rp.permission = (AccessPermission) permissionSelector.getSelectedItem();
                tableModel.permissions.add(rp);
                registrantModel.removeElement(rp.registrant);
                registrantSelector.setSelectedIndex(-1);
                registrantSelector.invalidate();
                addPanel.setVisible(registrantModel.getSize() > 0);
                tableModel.fireTableDataChanged();
            }
        });
        addPanel = new JPanel();
        addPanel.add(registrantSelector);
        addPanel.add(permissionSelector);
        addPanel.add(addButton);
        add(addPanel, BorderLayout.SOUTH);
    }
    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        permissionsTable.setEnabled(false);
    }
    public void setObjects(List<String> registrants, List<RegistrantAccessPermission> permissions) {
        List<String> filtered;
        if (registrants == null) {
            filtered = new ArrayList<String>();
        } else {
            filtered = new ArrayList<String>(registrants);
        }
        if (permissions == null) {
            permissions = new ArrayList<RegistrantAccessPermission>();
        }
        for (RegistrantAccessPermission rp : permissions) {
            filtered.remove(rp.registrant);
        }
        for (String registrant : filtered) {
            registrantModel.addElement(registrant);
        }
        tableModel.setPermissions(permissions);
        registrantSelector.setSelectedIndex(-1);
        permissionSelector.setSelectedIndex(-1);
        addPanel.setVisible(filtered.size() > 0);
    }
    public List<RegistrantAccessPermission> getPermissions() {
        return tableModel.permissions;
    }
    private class AccessPermissionEditor extends DefaultCellEditor {
        private static final long serialVersionUID = 1L;
        public AccessPermissionEditor() {
            super(new JComboBox(AccessPermission.values()));
        }
    }
}
src/com/gitblit/client/RegistrantPermissionsTableModel.java
New file
@@ -0,0 +1,120 @@
/*
 * Copyright 2012 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.client;
import java.util.ArrayList;
import java.util.List;
import javax.swing.table.AbstractTableModel;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.models.RegistrantAccessPermission;
/**
 * Table model of a registrant permissions.
 *
 * @author James Moger
 *
 */
public class RegistrantPermissionsTableModel extends AbstractTableModel {
    private static final long serialVersionUID = 1L;
    List<RegistrantAccessPermission> permissions;
    enum Columns {
        Registrant, Permission;
        @Override
        public String toString() {
            return name().replace('_', ' ');
        }
    }
    public RegistrantPermissionsTableModel() {
        this(new ArrayList<RegistrantAccessPermission>());
    }
    public RegistrantPermissionsTableModel(List<RegistrantAccessPermission> list) {
        setPermissions(list);
    }
    public void setPermissions(List<RegistrantAccessPermission> list) {
        this.permissions = list;
    }
    @Override
    public int getRowCount() {
        return permissions.size();
    }
    @Override
    public int getColumnCount() {
        return Columns.values().length;
    }
    @Override
    public String getColumnName(int column) {
        Columns col = Columns.values()[column];
        switch (col) {
        case Registrant:
            return Translation.get("gb.name");
        case Permission:
            return Translation.get("gb.permission");
        }
        return "";
    }
    /**
     * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
     *
     * @param columnIndex
     *            the column being queried
     * @return the Object.class
     */
    public Class<?> getColumnClass(int columnIndex) {
        if (columnIndex == Columns.Permission.ordinal()) {
            return AccessPermission.class;
        }
        return String.class;
    }
    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return columnIndex == Columns.Permission.ordinal();
    }
    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        RegistrantAccessPermission rp = permissions.get(rowIndex);
        Columns col = Columns.values()[columnIndex];
        switch (col) {
        case Registrant:
            return rp.registrant;
        case Permission:
            return rp.permission;
        }
        return null;
    }
    @Override
    public void setValueAt(Object o, int rowIndex, int columnIndex) {
        RegistrantAccessPermission rp = permissions.get(rowIndex);
        if (columnIndex == Columns.Permission.ordinal()) {
            rp.permission = (AccessPermission) o;
        }
    }
}
src/com/gitblit/client/RepositoriesPanel.java
@@ -49,6 +49,7 @@
import com.gitblit.Constants;
import com.gitblit.Constants.RpcRequest;
import com.gitblit.Keys;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.FeedModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.utils.StringUtils;
@@ -401,8 +402,8 @@
                gitblit.getPostReceiveScriptsInherited(null), null);
        dialog.setVisible(true);
        final RepositoryModel newRepository = dialog.getRepository();
        final List<String> permittedUsers = dialog.getPermittedUsers();
        final List<String> permittedTeams = dialog.getPermittedTeams();
        final List<RegistrantAccessPermission> permittedUsers = dialog.getUserAccessPermissions();
        final List<RegistrantAccessPermission> permittedTeams = dialog.getTeamAccessPermissions();
        if (newRepository == null) {
            return;
        }
@@ -452,10 +453,14 @@
                repository);
        dialog.setLocationRelativeTo(RepositoriesPanel.this);
        List<String> usernames = gitblit.getUsernames();
        List<String> members = gitblit.getPermittedUsernames(repository);
        dialog.setUsers(repository.owner, usernames, members);
        dialog.setTeams(gitblit.getTeamnames(), gitblit.getPermittedTeamnames(repository));
        dialog.setRepositories(gitblit.getRepositories());
        try {
            List<RegistrantAccessPermission> members = gitblit.getUserAccessPermissions(repository);
            dialog.setUsers(repository.owner, usernames, members);
            dialog.setTeams(gitblit.getTeamnames(), gitblit.getTeamAccessPermissions(repository));
            dialog.setRepositories(gitblit.getRepositories());
        } catch (IOException e) {
            //  TODO
        }
        dialog.setFederationSets(gitblit.getFederationSets(), repository.federationSets);
        List<String> allLocalBranches = new ArrayList<String>();
        allLocalBranches.add(Constants.DEFAULT_BRANCH);
@@ -471,8 +476,8 @@
        }
        dialog.setVisible(true);
        final RepositoryModel revisedRepository = dialog.getRepository();
        final List<String> permittedUsers = dialog.getPermittedUsers();
        final List<String> permittedTeams = dialog.getPermittedTeams();
        final List<RegistrantAccessPermission> permittedUsers = dialog.getUserAccessPermissions();
        final List<RegistrantAccessPermission> permittedTeams = dialog.getTeamAccessPermissions();
        if (revisedRepository == null) {
            return;
        }
src/com/gitblit/client/TeamsPanel.java
@@ -305,7 +305,7 @@
                gitblit.getSettings());
        dialog.setLocationRelativeTo(TeamsPanel.this);
        dialog.setTeams(gitblit.getTeams());
        dialog.setRepositories(gitblit.getRepositories(), new ArrayList<String>(team.repositories));
        dialog.setRepositories(gitblit.getRepositories(), team.getRepositoryPermissions());
        dialog.setUsers(gitblit.getUsernames(), team.users == null ? null : new ArrayList<String>(
                team.users));
        dialog.setPreReceiveScripts(gitblit.getPreReceiveScriptsUnused(null),
src/com/gitblit/client/UsersPanel.java
@@ -309,7 +309,7 @@
                gitblit.getSettings());
        dialog.setLocationRelativeTo(UsersPanel.this);
        dialog.setUsers(gitblit.getUsers());
        dialog.setRepositories(gitblit.getRepositories(), new ArrayList<String>(user.repositories));
        dialog.setRepositories(gitblit.getRepositories(), user.getRepositoryPermissions());
        dialog.setTeams(gitblit.getTeams(), user.teams == null ? null : new ArrayList<TeamModel>(
                user.teams));
        dialog.setVisible(true);
src/com/gitblit/client/UsersTableModel.java
@@ -110,8 +110,8 @@
            return (model.teams == null || model.teams.size() == 0) ? "" : String
                    .valueOf(model.teams.size());
        case Repositories:
            return (model.repositories == null || model.repositories.size() == 0) ? "" : String
                    .valueOf(model.repositories.size());
            return (model.permissions == null || model.permissions.size() == 0) ? "" : String
                    .valueOf(model.permissions.size());
        }
        return null;
    }
src/com/gitblit/models/RegistrantAccessPermission.java
New file
@@ -0,0 +1,60 @@
/*
 * Copyright 2012 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.models;
import java.io.Serializable;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.utils.StringUtils;
/**
 * Represents a Registrant-AccessPermission tuple.
 *
 * @author James Moger
 */
public class RegistrantAccessPermission implements Serializable, Comparable<RegistrantAccessPermission> {
    private static final long serialVersionUID = 1L;
    public String registrant;
    public AccessPermission permission;
    public RegistrantType type;
    public RegistrantAccessPermission() {
    }
    public RegistrantAccessPermission(String registrant, AccessPermission permission, RegistrantType type) {
        this.registrant = registrant;
        this.permission = permission;
        this.type = type;
    }
    @Override
    public int compareTo(RegistrantAccessPermission p) {
        switch (type) {
        case REPOSITORY:
            return StringUtils.compareRepositoryNames(registrant, p.registrant);
        default:
            return registrant.toLowerCase().compareTo(p.registrant.toLowerCase());
        }
    }
    @Override
    public String toString() {
        return permission.asRole(registrant);
    }
}
src/com/gitblit/models/RepositoryAccessPermission.java
File was deleted
src/com/gitblit/models/TeamAccessPermission.java
File was deleted
src/com/gitblit/models/TeamModel.java
@@ -27,6 +27,7 @@
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.Constants.Unused;
/**
@@ -93,10 +94,10 @@
     * 
     * @return the team's list of permissions
     */
    public List<RepositoryAccessPermission> getRepositoryPermissions() {
        List<RepositoryAccessPermission> list = new ArrayList<RepositoryAccessPermission>();
    public List<RegistrantAccessPermission> getRepositoryPermissions() {
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
        for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
            list.add(new RepositoryAccessPermission(entry.getKey(), entry.getValue()));
            list.add(new RegistrantAccessPermission(entry.getKey(), entry.getValue(), RegistrantType.REPOSITORY));
        }
        Collections.sort(list);
        return list;
src/com/gitblit/models/UserAccessPermission.java
File was deleted
src/com/gitblit/models/UserModel.java
@@ -28,6 +28,7 @@
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.Constants.Unused;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
@@ -133,10 +134,10 @@
     * 
     * @return the user's list of permissions
     */
    public List<RepositoryAccessPermission> getRepositoryPermissions() {
        List<RepositoryAccessPermission> list = new ArrayList<RepositoryAccessPermission>();
    public List<RegistrantAccessPermission> getRepositoryPermissions() {
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
        for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
            list.add(new RepositoryAccessPermission(entry.getKey(), entry.getValue()));
            list.add(new RegistrantAccessPermission(entry.getKey(), entry.getValue(), RegistrantType.REPOSITORY));
        }
        Collections.sort(list);
        return list;
@@ -208,10 +209,10 @@
        // and the permissions of teams of which the user belongs
        AccessPermission permission = AccessPermission.NONE;
        if (permissions.containsKey(repository.name.toLowerCase())) {
            // exact repository permission specified
            // exact repository permission specified, use it
            AccessPermission p = permissions.get(repository.name.toLowerCase());
            if (p != null) {
                permission = p;
                return p;
            }
        } else {
            // search for regex permission match
src/com/gitblit/utils/RpcUtils.java
@@ -25,6 +25,7 @@
import com.gitblit.Constants;
import com.gitblit.Constants.RpcRequest;
import com.gitblit.GitBlitException.UnknownRequestException;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
@@ -69,6 +70,9 @@
    }.getType();
    private static final Type BRANCHES_TYPE = new TypeToken<Map<String, Collection<String>>>() {
    }.getType();
    public static final Type REGISTRANT_PERMISSIONS_TYPE = new TypeToken<Collection<RegistrantAccessPermission>>() {
    }.getType();
    /**
@@ -357,25 +361,42 @@
        Collection<String> list = JsonUtils.retrieveJson(url, NAMES_TYPE, account, password);
        return new ArrayList<String>(list);
    }
    /**
     * Sets the repository user membership list.
     * Retrieves the list of user access permissions for the specified repository.
     * 
     * @param repository
     * @param memberships
     * @param serverUrl
     * @param account
     * @param password
     * @return list of User-AccessPermission tuples
     * @throws IOException
     */
    public static List<RegistrantAccessPermission> getRepositoryMemberPermissions(RepositoryModel repository,
            String serverUrl, String account, char [] password) throws IOException {
        String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORY_MEMBER_PERMISSIONS, repository.name);
        Collection<RegistrantAccessPermission> list = JsonUtils.retrieveJson(url, REGISTRANT_PERMISSIONS_TYPE, account, password);
        return new ArrayList<RegistrantAccessPermission>(list);
    }
    /**
     * Sets the repository user access permissions
     *
     * @param repository
     * @param permissions
     * @param serverUrl
     * @param account
     * @param password
     * @return true if the action succeeded
     * @throws IOException
     */
    public static boolean setRepositoryMembers(RepositoryModel repository,
            List<String> memberships, String serverUrl, String account, char[] password)
    public static boolean setRepositoryMemberPermissions(RepositoryModel repository,
            List<RegistrantAccessPermission> permissions, String serverUrl, String account, char[] password)
            throws IOException {
        return doAction(RpcRequest.SET_REPOSITORY_MEMBERS, repository.name, memberships, serverUrl,
        return doAction(RpcRequest.SET_REPOSITORY_MEMBER_PERMISSIONS, repository.name, permissions, serverUrl,
                account, password);
    }
    /**
     * Retrieves the list of teams that can access the specified repository.
     * 
@@ -392,25 +413,42 @@
        Collection<String> list = JsonUtils.retrieveJson(url, NAMES_TYPE, account, password);
        return new ArrayList<String>(list);
    }
    /**
     * Sets the repository team membership list.
     * Retrieves the list of team access permissions for the specified repository.
     * 
     * @param repository
     * @param teams
     * @param serverUrl
     * @param account
     * @param password
     * @return list of Team-AccessPermission tuples
     * @throws IOException
     */
    public static List<RegistrantAccessPermission> getRepositoryTeamPermissions(RepositoryModel repository,
            String serverUrl, String account, char [] password) throws IOException {
        String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORY_TEAM_PERMISSIONS, repository.name);
        Collection<RegistrantAccessPermission> list = JsonUtils.retrieveJson(url, REGISTRANT_PERMISSIONS_TYPE, account, password);
        return new ArrayList<RegistrantAccessPermission>(list);
    }
    /**
     * Sets the repository team access permissions
     *
     * @param repository
     * @param permissions
     * @param serverUrl
     * @param account
     * @param password
     * @return true if the action succeeded
     * @throws IOException
     */
    public static boolean setRepositoryTeams(RepositoryModel repository,
            List<String> teams, String serverUrl, String account, char[] password)
    public static boolean setRepositoryTeamPermissions(RepositoryModel repository,
            List<RegistrantAccessPermission> permissions, String serverUrl, String account, char[] password)
            throws IOException {
        return doAction(RpcRequest.SET_REPOSITORY_TEAMS, repository.name, teams, serverUrl,
        return doAction(RpcRequest.SET_REPOSITORY_TEAM_PERMISSIONS, repository.name, permissions, serverUrl,
                account, password);
    }
    /**
     * Retrieves the list of federation registrations. These are the list of
     * registrations that this Gitblit instance is pulling from.
src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -353,4 +353,5 @@
gb.pushPermission = {0} (push)
gb.createPermission = {0} (push, ref creation)
gb.deletePermission = {0} (push, ref creation+deletion)
gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
gb.permission = permission
src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -54,9 +54,8 @@
import com.gitblit.GitBlit;
import com.gitblit.GitBlitException;
import com.gitblit.Keys;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.TeamAccessPermission;
import com.gitblit.models.UserAccessPermission;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
@@ -64,8 +63,7 @@
import com.gitblit.wicket.StringChoiceRenderer;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.BulletListPanel;
import com.gitblit.wicket.panels.TeamPermissionsPanel;
import com.gitblit.wicket.panels.UserPermissionsPanel;
import com.gitblit.wicket.panels.RegistrantPermissionsPanel;
public class EditRepositoryPage extends RootSubPage {
@@ -117,8 +115,8 @@
        List<String> indexedBranches = new ArrayList<String>();
        List<String> federationSets = new ArrayList<String>();
        final List<UserAccessPermission> repositoryUsers = new ArrayList<UserAccessPermission>();
        final List<TeamAccessPermission> repositoryTeams = new ArrayList<TeamAccessPermission>();
        final List<RegistrantAccessPermission> repositoryUsers = new ArrayList<RegistrantAccessPermission>();
        final List<RegistrantAccessPermission> repositoryTeams = new ArrayList<RegistrantAccessPermission>();
        List<String> preReceiveScripts = new ArrayList<String>();
        List<String> postReceiveScripts = new ArrayList<String>();
@@ -146,8 +144,10 @@
        final String oldName = repositoryModel.name;
        UserPermissionsPanel usersPalette = new UserPermissionsPanel("users", repositoryUsers, getAccessPermissions());
        TeamPermissionsPanel teamsPalette = new TeamPermissionsPanel("teams", repositoryTeams, getAccessPermissions());
        RegistrantPermissionsPanel usersPalette = new RegistrantPermissionsPanel("users",
                GitBlit.self().getAllUsernames(), repositoryUsers, getAccessPermissions());
        RegistrantPermissionsPanel teamsPalette = new RegistrantPermissionsPanel("teams",
                GitBlit.self().getAllTeamnames(), repositoryTeams, getAccessPermissions());
        // indexed local branches palette
        List<String> allLocalBranches = new ArrayList<String>();
src/com/gitblit/wicket/pages/EditTeamPage.java
@@ -39,15 +39,15 @@
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.GitBlit;
import com.gitblit.GitBlitException;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.RepositoryAccessPermission;
import com.gitblit.models.TeamModel;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.RequiresAdminRole;
import com.gitblit.wicket.StringChoiceRenderer;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.BulletListPanel;
import com.gitblit.wicket.panels.RepositoryPermissionsPanel;
import com.gitblit.wicket.panels.RegistrantPermissionsPanel;
@RequiresAdminRole
public class EditTeamPage extends RootSubPage {
@@ -98,7 +98,7 @@
        List<String> postReceiveScripts = new ArrayList<String>();
        final String oldName = teamModel.name;
        final List<RepositoryAccessPermission> permissions = teamModel.getRepositoryPermissions();
        final List<RegistrantAccessPermission> permissions = teamModel.getRepositoryPermissions();
        // users palette
        final Palette<String> users = new Palette<String>("users", new ListModel<String>(
@@ -147,8 +147,8 @@
                    }
                }
                // update team permissions
                for (RepositoryAccessPermission repositoryPermission : permissions) {
                    teamModel.setRepositoryPermission(repositoryPermission.repository, repositoryPermission.permission);
                for (RegistrantAccessPermission repositoryPermission : permissions) {
                    teamModel.setRepositoryPermission(repositoryPermission.registrant, repositoryPermission.permission);
                }
                Iterator<String> selectedUsers = users.getSelectedChoices();
@@ -224,7 +224,7 @@
                : StringUtils.flattenStrings(teamModel.mailingLists, " "));
        form.add(new TextField<String>("mailingLists", mailingLists));
        form.add(new RepositoryPermissionsPanel("repositories", permissions, getAccessPermissions()));
        form.add(new RegistrantPermissionsPanel("repositories", repos, permissions, getAccessPermissions()));
        form.add(preReceivePalette);
        form.add(new BulletListPanel("inheritedPreReceive", "inherited", GitBlit.self()
                .getPreReceiveScriptsInherited(null)));
src/com/gitblit/wicket/pages/EditUserPage.java
@@ -38,15 +38,15 @@
import com.gitblit.GitBlit;
import com.gitblit.GitBlitException;
import com.gitblit.Keys;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.RepositoryAccessPermission;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.RequiresAdminRole;
import com.gitblit.wicket.StringChoiceRenderer;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.RepositoryPermissionsPanel;
import com.gitblit.wicket.panels.RegistrantPermissionsPanel;
@RequiresAdminRole
public class EditUserPage extends RootSubPage {
@@ -93,6 +93,8 @@
                repos.add(repo);
            }
        }
        StringUtils.sortRepositorynames(repos);
        List<String> userTeams = new ArrayList<String>();
        for (TeamModel team : userModel.teams) {
            userTeams.add(team.name);
@@ -100,7 +102,7 @@
        Collections.sort(userTeams);
        
        final String oldName = userModel.username;
        final List<RepositoryAccessPermission> permissions = userModel.getRepositoryPermissions();
        final List<RegistrantAccessPermission> permissions = userModel.getRepositoryPermissions();
        final Palette<String> teams = new Palette<String>("teams", new ListModel<String>(
                new ArrayList<String>(userTeams)), new CollectionModel<String>(GitBlit.self()
@@ -171,8 +173,8 @@
                }
                // update user permissions
                for (RepositoryAccessPermission repositoryPermission : permissions) {
                    userModel.setRepositoryPermission(repositoryPermission.repository, repositoryPermission.permission);
                for (RegistrantAccessPermission repositoryPermission : permissions) {
                    userModel.setRepositoryPermission(repositoryPermission.registrant, repositoryPermission.permission);
                }
                Iterator<String> selectedTeams = teams.getSelectedChoices();
@@ -234,7 +236,7 @@
        form.add(new CheckBox("canFork"));
        form.add(new CheckBox("canCreate"));
        form.add(new CheckBox("excludeFromFederation"));
        form.add(new RepositoryPermissionsPanel("repositories", permissions, getAccessPermissions()));
        form.add(new RegistrantPermissionsPanel("repositories",    repos, permissions, getAccessPermissions()));
        form.add(teams.setEnabled(editTeams));
        form.add(new Button("save"));
src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
File was renamed from src/com/gitblit/wicket/panels/RepositoryPermissionsPanel.html
@@ -9,13 +9,13 @@
    <div wicket:id="permissionRow">
        <div style="padding-top:10px" class="row-fluid">
            <span class="span8" wicket:id="repository"></span> <select class="input-medium" wicket:id="permission"></select>
            <span class="span8" wicket:id="registrant"></span> <select class="input-medium" wicket:id="permission"></select>
        </div>
    </div>
    <div style="padding-top:15px;" class="row-fluid">
        <form class="well form-inline" wicket:id="addPermissionForm">
            <select class="input-large" wicket:id="repository"></select> <select class="input-medium" wicket:id="permission"></select> <input class="btn btn-success" type="submit" value="Add" wicket:message="value:gb.add" wicket:id="addPermissionButton"/>
            <select class="input-large" wicket:id="registrant"></select> <select class="input-medium" wicket:id="permission"></select> <input class="btn btn-success" type="submit" value="Add" wicket:message="value:gb.add" wicket:id="addPermissionButton"/>
        </form>
    </div>    
    
src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
File was renamed from src/com/gitblit/wicket/panels/RepositoryPermissionsPanel.java
@@ -15,6 +15,7 @@
 */
package com.gitblit.wicket.panels;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
@@ -35,49 +36,48 @@
import org.apache.wicket.model.IModel;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.GitBlit;
import com.gitblit.models.RepositoryAccessPermission;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.utils.DeepCopier;
/**
 * Allows user to manipulate repository access permissions.
 * Allows user to manipulate registrant access permissions.
 * 
 * @author James Moger
 *
 */
public class RepositoryPermissionsPanel extends BasePanel {
public class RegistrantPermissionsPanel extends BasePanel {
    private static final long serialVersionUID = 1L;
    public RepositoryPermissionsPanel(String wicketId, final List<RepositoryAccessPermission> permissions, final Map<AccessPermission, String> translations) {
    public RegistrantPermissionsPanel(String wicketId, List<String> allRegistrants, final List<RegistrantAccessPermission> permissions, final Map<AccessPermission, String> translations) {
        super(wicketId);
        
        // update existing permissions repeater
        RefreshingView<RepositoryAccessPermission> dataView = new RefreshingView<RepositoryAccessPermission>("permissionRow") {
        RefreshingView<RegistrantAccessPermission> dataView = new RefreshingView<RegistrantAccessPermission>("permissionRow") {
            private static final long serialVersionUID = 1L;
        
            @Override
            protected Iterator<IModel<RepositoryAccessPermission>> getItemModels() {
            protected Iterator<IModel<RegistrantAccessPermission>> getItemModels() {
                // the iterator returns RepositoryPermission objects, but we need it to
                // return models
                return new ModelIteratorAdapter<RepositoryAccessPermission>(permissions.iterator()) {
                return new ModelIteratorAdapter<RegistrantAccessPermission>(permissions.iterator()) {
                    @Override
                    protected IModel<RepositoryAccessPermission> model(RepositoryAccessPermission permission) {
                        return new CompoundPropertyModel<RepositoryAccessPermission>(permission);
                    protected IModel<RegistrantAccessPermission> model(RegistrantAccessPermission permission) {
                        return new CompoundPropertyModel<RegistrantAccessPermission>(permission);
                    }
                };
            }
            @Override
            protected Item<RepositoryAccessPermission> newItem(String id, int index, IModel<RepositoryAccessPermission> model) {
            protected Item<RegistrantAccessPermission> newItem(String id, int index, IModel<RegistrantAccessPermission> model) {
                // this item sets markup class attribute to either 'odd' or
                // 'even' for decoration
                return new OddEvenItem<RepositoryAccessPermission>(id, index, model);
                return new OddEvenItem<RegistrantAccessPermission>(id, index, model);
            }
            
            public void populateItem(final Item<RepositoryAccessPermission> item) {
                final RepositoryAccessPermission entry = item.getModelObject();
                item.add(new Label("repository", entry.repository));
            public void populateItem(final Item<RegistrantAccessPermission> item) {
                final RegistrantAccessPermission entry = item.getModelObject();
                item.add(new Label("registrant", entry.registrant));
                // use ajax to get immediate update of permission level change
                // otherwise we can lose it if they change levels and then add
@@ -99,16 +99,16 @@
        add(dataView);
        setOutputMarkupId(true);
        // filter out repositories we already have permissions for
        final List<String> repositories = GitBlit.self().getRepositoryList();
        for (RepositoryAccessPermission rp : permissions) {
            repositories.remove(rp.repository);
        // filter out registrants we already have permissions for
        final List<String> registrants = new ArrayList<String>(allRegistrants);
        for (RegistrantAccessPermission rp : permissions) {
            registrants.remove(rp.registrant);
        }
        // add new permission form
        IModel<RepositoryAccessPermission> addPermissionModel = new CompoundPropertyModel<RepositoryAccessPermission>(new RepositoryAccessPermission());
        Form<RepositoryAccessPermission> addPermissionForm = new Form<RepositoryAccessPermission>("addPermissionForm", addPermissionModel);
        addPermissionForm.add(new DropDownChoice<String>("repository", repositories));
        IModel<RegistrantAccessPermission> addPermissionModel = new CompoundPropertyModel<RegistrantAccessPermission>(new RegistrantAccessPermission());
        Form<RegistrantAccessPermission> addPermissionForm = new Form<RegistrantAccessPermission>("addPermissionForm", addPermissionModel);
        addPermissionForm.add(new DropDownChoice<String>("registrant", registrants));
        addPermissionForm.add(new DropDownChoice<AccessPermission>("permission", Arrays
                .asList(AccessPermission.NEWPERMISSIONS), new AccessPermissionRenderer(translations)));
        AjaxButton button = new AjaxButton("addPermissionButton", addPermissionForm) {
@@ -118,20 +118,20 @@
            @Override
            protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
                // add permission to our list
                RepositoryAccessPermission rp = (RepositoryAccessPermission) form.getModel().getObject();
                RegistrantAccessPermission rp = (RegistrantAccessPermission) form.getModel().getObject();
                permissions.add(DeepCopier.copy(rp));
                
                // remove repository from available choices
                repositories.remove(rp.repository);
                // remove registrant from available choices
                registrants.remove(rp.registrant);
                
                // force the panel to refresh
                target.addComponent(RepositoryPermissionsPanel.this);
                target.addComponent(RegistrantPermissionsPanel.this);
            }
        };
        addPermissionForm.add(button);
        
        // only show add permission form if we have a repository choice
        add(addPermissionForm.setVisible(repositories.size() > 0));
        // only show add permission form if we have a registrant choice
        add(addPermissionForm.setVisible(registrants.size() > 0));
    }
    
    private class AccessPermissionRenderer implements IChoiceRenderer<AccessPermission> {
src/com/gitblit/wicket/panels/TeamPermissionsPanel.html
File was deleted
src/com/gitblit/wicket/panels/TeamPermissionsPanel.java
File was deleted
src/com/gitblit/wicket/panels/UserPermissionsPanel.html
File was deleted
src/com/gitblit/wicket/panels/UserPermissionsPanel.java
File was deleted
src/com/gitblit/wicket/panels/UsersPanel.java
@@ -84,7 +84,7 @@
                item.add(new Label("accesslevel", entry.canAdmin() ? "administrator" : ""));
                item.add(new Label("teams", entry.teams.size() > 0 ? ("" + entry.teams.size()) : ""));
                item.add(new Label("repositories",
                        entry.repositories.size() > 0 ? ("" + entry.repositories.size()) : ""));
                        entry.permissions.size() > 0 ? ("" + entry.permissions.size()) : ""));
                Fragment userLinks = new Fragment("userLinks", "userAdminLinks", this);
                userLinks.add(new BookmarkablePageLink<Void>("editUser", EditUserPage.class,
                        WicketUtils.newUsernameParameter(entry.username)));
tests/com/gitblit/tests/RpcTests.java
@@ -32,11 +32,14 @@
import org.junit.BeforeClass;
import org.junit.Test;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.GitBlitException.UnauthorizedException;
import com.gitblit.Keys;
import com.gitblit.RpcServlet;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
@@ -180,6 +183,7 @@
        String originalName = model.name;
        model.name = "garbagerepo2.git";
        model.accessRestriction = AccessRestrictionType.PUSH;
        model.authorizationControl = AuthorizationControl.NAMED;
        assertTrue("Failed to update repository!", RpcUtils.updateRepository(originalName, model,
                url, account, password.toCharArray()));
@@ -192,20 +196,21 @@
        UserModel testMember = new UserModel("justadded");
        assertTrue(RpcUtils.createUser(testMember, url, account, password.toCharArray()));
        List<String> members = RpcUtils.getRepositoryMembers(retrievedRepository, url, account,
        List<RegistrantAccessPermission> permissions = RpcUtils.getRepositoryMemberPermissions(retrievedRepository, url, account,
                password.toCharArray());
        assertEquals("Membership roster is not empty!", 0, members.size());
        members.add(testMember.username);
        assertEquals("Membership permissions is not empty!", 0, permissions.size());
        permissions.add(new RegistrantAccessPermission(testMember.username, AccessPermission.PUSH, RegistrantType.USER));
        assertTrue(
                "Failed to set memberships!",
                RpcUtils.setRepositoryMembers(retrievedRepository, members, url, account,
                "Failed to set member permissions!",
                RpcUtils.setRepositoryMemberPermissions(retrievedRepository, permissions, url, account,
                        password.toCharArray()));
        members = RpcUtils.getRepositoryMembers(retrievedRepository, url, account,
        permissions = RpcUtils.getRepositoryMemberPermissions(retrievedRepository, url, account,
                password.toCharArray());
        boolean foundMember = false;
        for (String member : members) {
            if (member.equalsIgnoreCase(testMember.username)) {
        for (RegistrantAccessPermission permission : permissions) {
            if (permission.registrant.equalsIgnoreCase(testMember.username)) {
                foundMember = true;
                assertEquals(AccessPermission.PUSH, permission.permission);
                break;
            }
        }
@@ -281,7 +286,11 @@
        assertTrue(helloworldTeams.contains(aTeam.name));
        // set no teams
        assertTrue(RpcUtils.setRepositoryTeams(helloworld, new ArrayList<String>(), url, account,
        List<RegistrantAccessPermission> permissions = new ArrayList<RegistrantAccessPermission>();
        for (String team : helloworldTeams) {
            permissions.add(new RegistrantAccessPermission(team, AccessPermission.NONE, RegistrantType.TEAM));
        }
        assertTrue(RpcUtils.setRepositoryTeamPermissions(helloworld, permissions, url, account,
                password.toCharArray()));
        helloworldTeams = RpcUtils.getRepositoryTeams(helloworld, url, account,
                password.toCharArray());