James Moger
2012-10-31 644bdd5a59a5ed5fbf93a0765f92608b0530c16a
Improve transparency of permissions by indicating permission source
12 files modified
342 ■■■■■ changed files
src/com/gitblit/Constants.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/GitBlit.java 64 ●●●●● patch | view | raw | blame | history
src/com/gitblit/client/GitblitClient.java 49 ●●●● patch | view | raw | blame | history
src/com/gitblit/client/RegistrantPermissionsPanel.java 23 ●●●● patch | view | raw | blame | history
src/com/gitblit/client/RegistrantPermissionsTableModel.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/models/RegistrantAccessPermission.java 15 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/TeamModel.java 36 ●●●● patch | view | raw | blame | history
src/com/gitblit/models/UserModel.java 102 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/GitBlitWebApp.properties 8 ●●●● 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 31 ●●●● patch | view | raw | blame | history
tests/com/gitblit/tests/RpcTests.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/Constants.java
@@ -387,7 +387,7 @@
    }
    
    public static enum PermissionType {
        EXPLICIT, OWNER, REGEX;
        EXPLICIT, OWNER, ADMINISTRATOR, TEAM, REGEX;
    }
    
    public static enum GCStatus {
src/com/gitblit/GitBlit.java
@@ -80,7 +80,6 @@
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.Constants.FederationToken;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
@@ -665,41 +664,22 @@
    }
    /**
     * Returns the list of users and their access permissions for the specified repository.
     * Returns the list of users and their access permissions for the specified
     * repository including permission source information such as the team or
     * regular expression which sets the permission.
     * 
     * @param repository
     * @return a list of User-AccessPermission tuples
     * @return a list of RegistrantAccessPermissions
     */
    public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
        Set<RegistrantAccessPermission> permissions = new LinkedHashSet<RegistrantAccessPermission>();
        if (!StringUtils.isEmpty(repository.owner)) {
            UserModel owner = userService.getUserModel(repository.owner);
            if (owner != null) {
                permissions.add(new RegistrantAccessPermission(owner.username, AccessPermission.REWIND, PermissionType.OWNER, RegistrantType.USER, false));
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
        for (UserModel user : userService.getAllUsers()) {
            RegistrantAccessPermission ap = user.getRepositoryPermission(repository);
            if (ap.permission.exceeds(AccessPermission.NONE)) {
                list.add(ap);
            }
        }
        if (repository.isPersonalRepository()) {
            UserModel owner = userService.getUserModel(repository.projectPath.substring(1));
            if (owner != null) {
                permissions.add(new RegistrantAccessPermission(owner.username, AccessPermission.REWIND, PermissionType.OWNER, RegistrantType.USER, false));
            }
        }
        for (String user : userService.getUsernamesForRepositoryRole(repository.name)) {
            UserModel model = userService.getUserModel(user);
            AccessPermission ap = model.getRepositoryPermission(repository);
            PermissionType pType = PermissionType.REGEX;
            boolean editable = false;
            if (repository.isOwner(model.username)) {
                pType = PermissionType.OWNER;
            } else if (repository.isUsersPersonalRepository(model.username)) {
                pType = PermissionType.OWNER;
            } else if (model.hasExplicitRepositoryPermission(repository.name)) {
                pType = PermissionType.EXPLICIT;
                editable = true;
            }
            permissions.add(new RegistrantAccessPermission(user, ap, pType, RegistrantType.USER, editable));
        }
        return new ArrayList<RegistrantAccessPermission>(permissions);
        return list;
    }
    
    /**
@@ -823,25 +803,23 @@
    }
    
    /**
     * Returns the list of teams and their access permissions for the specified repository.
     * Returns the list of teams and their access permissions for the specified
     * repository including the source of the permission such as the admin flag
     * or a regular expression.
     * 
     * @param repository
     * @return a list of Team-AccessPermission tuples
     * @return a list of RegistrantAccessPermissions
     */
    public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
        List<RegistrantAccessPermission> permissions = new ArrayList<RegistrantAccessPermission>();
        for (String team : userService.getTeamnamesForRepositoryRole(repository.name)) {
            TeamModel model = userService.getTeamModel(team);
            AccessPermission ap = model.getRepositoryPermission(repository);
            PermissionType pType = PermissionType.REGEX;
            boolean editable = false;
            if (model.hasExplicitRepositoryPermission(repository.name)) {
                pType = PermissionType.EXPLICIT;
                editable = true;
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
        for (TeamModel team : userService.getAllTeams()) {
            RegistrantAccessPermission ap = team.getRepositoryPermission(repository);
            if (ap.permission.exceeds(AccessPermission.NONE)) {
                list.add(ap);
            }
            permissions.add(new RegistrantAccessPermission(team, ap, pType, RegistrantType.TEAM, editable));
        }
        return permissions;
        Collections.sort(list);
        return list;
    }
    
    /**
src/com/gitblit/client/GitblitClient.java
@@ -31,8 +31,6 @@
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.GitBlitException.ForbiddenException;
import com.gitblit.GitBlitException.NotAllowedException;
import com.gitblit.GitBlitException.UnauthorizedException;
@@ -508,38 +506,15 @@
    }
    
    public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
        Set<RegistrantAccessPermission> list = new LinkedHashSet<RegistrantAccessPermission>();
        if (!StringUtils.isEmpty(repository.owner)) {
            UserModel owner = getUser(repository.owner);
            if (owner != null) {
                list.add(new RegistrantAccessPermission(owner.username, AccessPermission.REWIND, PermissionType.OWNER, RegistrantType.USER, false));
            }
        }
        if (repository.isPersonalRepository()) {
            UserModel owner = getUser(repository.projectPath.substring(1));
            if (owner != null) {
                list.add(new RegistrantAccessPermission(owner.username, AccessPermission.REWIND, PermissionType.OWNER, RegistrantType.USER, false));
            }
        }
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
        for (UserModel user : getUsers()) {
            if (user.hasRepositoryPermission(repository.name)) {
                AccessPermission ap = user.getRepositoryPermission(repository);
                PermissionType pType = PermissionType.REGEX;
                boolean editable = false;
                if (repository.isOwner(user.username)) {
                    pType = PermissionType.OWNER;
                } else if (repository.isUsersPersonalRepository(user.username)) {
                    pType = PermissionType.OWNER;
                } else if (user.hasExplicitRepositoryPermission(repository.name)) {
                    pType = PermissionType.EXPLICIT;
                    editable = true;
                }
                list.add(new RegistrantAccessPermission(user.username, ap, pType, RegistrantType.USER, editable));
            RegistrantAccessPermission ap = user.getRepositoryPermission(repository);
            if (ap.permission.exceeds(AccessPermission.NONE)) {
                list.add(ap);
            }
        }
        List<RegistrantAccessPermission> raps = new ArrayList<RegistrantAccessPermission>(list);
        Collections.sort(raps);
        return raps;
        Collections.sort(list);
        return list;
    }
    public boolean setUserAccessPermissions(RepositoryModel repository, List<RegistrantAccessPermission> permissions) throws IOException {
@@ -572,15 +547,9 @@
    public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
        for (TeamModel team : allTeams) {
            if (team.hasRepositoryPermission(repository.name)) {
                AccessPermission ap = team.getRepositoryPermission(repository);
                PermissionType pType = PermissionType.REGEX;
                boolean editable = false;
                if (team.hasExplicitRepositoryPermission(repository.name)) {
                    pType = PermissionType.EXPLICIT;
                    editable = true;
                }
                list.add(new RegistrantAccessPermission(team.name, ap, pType, RegistrantType.TEAM, editable));
            RegistrantAccessPermission ap = team.getRepositoryPermission(repository);
            if (ap.permission.exceeds(AccessPermission.NONE)) {
                list.add(ap);
            }
        }
        Collections.sort(list);
src/com/gitblit/client/RegistrantPermissionsPanel.java
@@ -19,6 +19,7 @@
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
@@ -136,6 +137,12 @@
                // only remove editable duplicates
                // this allows for specifying an explicit permission
                filtered.remove(rp.registrant);
            } else if (rp.isAdmin()) {
                // administrators can not have their permission changed
                filtered.remove(rp.registrant);
            } else if (rp.isOwner()) {
                // owners can not have their permission changed
                filtered.remove(rp.registrant);
            }
        }
        for (String registrant : filtered) {
@@ -172,15 +179,23 @@
        @Override
        protected void setValue(Object value) {
            PermissionType pType = (PermissionType) value;
            switch (pType) {
            RegistrantAccessPermission ap = (RegistrantAccessPermission) value;
            switch (ap.permissionType) {
            case ADMINISTRATOR:
                setText(ap.source == null ? Translation.get("gb.administrator") : ap.source);
                setToolTipText(Translation.get("gb.administratorPermission"));
                break;
            case OWNER:
                setText("owner");
                setText(Translation.get("gb.owner"));
                setToolTipText(Translation.get("gb.ownerPermission"));
                break;
            case TEAM:
                setText(ap.source == null ? Translation.get("gb.team") : ap.source);
                setToolTipText(MessageFormat.format(Translation.get("gb.teamPermission"), ap.source));
                break;
            case REGEX:
                setText("regex");
                setToolTipText(Translation.get("gb.regexPermission"));
                setToolTipText(MessageFormat.format(Translation.get("gb.regexPermission"), ap.source));
                break;
            default:
                setText("");
src/com/gitblit/client/RegistrantPermissionsTableModel.java
@@ -91,7 +91,7 @@
        if (columnIndex == Columns.Permission.ordinal()) {
            return AccessPermission.class;
        } else if (columnIndex == Columns.Type.ordinal()) {
            return Boolean.class;
            return RegistrantAccessPermission.class;
        }
        return String.class;
    }
@@ -117,7 +117,7 @@
        case Registrant:
            return rp.registrant;
        case Type:
            return rp.permissionType;
            return rp;
        case Permission:
            return rp.permission;
        }
src/com/gitblit/models/RegistrantAccessPermission.java
@@ -36,6 +36,10 @@
    public RegistrantType registrantType;
    public PermissionType permissionType;
    public boolean isEditable;
    public String source;
    public RegistrantAccessPermission() {
    }
    public RegistrantAccessPermission(RegistrantType registrantType) {
        this.registrantType = registrantType;
@@ -43,14 +47,23 @@
        this.isEditable = true;
    }
    
    public RegistrantAccessPermission(String registrant, AccessPermission permission, PermissionType permissionType, RegistrantType registrantType, boolean isEditable) {
    public RegistrantAccessPermission(String registrant, AccessPermission permission, PermissionType permissionType, RegistrantType registrantType, String source, boolean isEditable) {
        this.registrant = registrant;
        this.permission = permission;
        this.permissionType = permissionType;
        this.registrantType = registrantType;
        this.source = source;
        this.isEditable = isEditable;
    }
    
    public boolean isAdmin() {
        return PermissionType.ADMINISTRATOR.equals(permissionType);
    }
    public boolean isOwner() {
        return PermissionType.OWNER.equals(permissionType);
    }
    @Override
    public int compareTo(RegistrantAccessPermission p) {
        switch (registrantType) {
src/com/gitblit/models/TeamModel.java
@@ -100,13 +100,15 @@
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
        for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
            String registrant = entry.getKey();
            String source = null;
            boolean editable = true;
            PermissionType pType = PermissionType.EXPLICIT;
            if (StringUtils.findInvalidCharacter(registrant) != null) {
                // a regex will have at least 1 invalid character
                pType = PermissionType.REGEX;
                source = registrant;
            }
            list.add(new RegistrantAccessPermission(registrant, entry.getValue(), pType, RegistrantType.REPOSITORY, editable));
            list.add(new RegistrantAccessPermission(registrant, entry.getValue(), pType, RegistrantType.REPOSITORY, source, editable));
        }
        Collections.sort(list);
        return list;
@@ -184,13 +186,27 @@
        repositories.add(repository.toLowerCase());
    }
    
    public AccessPermission getRepositoryPermission(RepositoryModel repository) {
        AccessPermission permission = AccessPermission.NONE;
    public RegistrantAccessPermission getRepositoryPermission(RepositoryModel repository) {
        RegistrantAccessPermission ap = new RegistrantAccessPermission();
        ap.registrant = name;
        ap.registrantType = RegistrantType.TEAM;
        ap.permission = AccessPermission.NONE;
        ap.isEditable = false;
        if (canAdmin) {
            ap.permissionType = PermissionType.ADMINISTRATOR;
            ap.permission = AccessPermission.REWIND;
            return ap;
        }
        if (permissions.containsKey(repository.name.toLowerCase())) {
            // exact repository permission specified
            AccessPermission p = permissions.get(repository.name.toLowerCase());
            if (p != null) {
                permission = p;
                ap.permissionType = PermissionType.EXPLICIT;
                ap.permission = p;
                ap.isEditable = true;
                return ap;
            }
        } else {
            // search for case-insensitive regex permission match
@@ -198,20 +214,22 @@
                if (StringUtils.matchesIgnoreCase(repository.name, key)) {
                    AccessPermission p = permissions.get(key);
                    if (p != null) {
                        permission = p;
                        // take first match
                        break;
                        ap.permissionType = PermissionType.REGEX;
                        ap.permission = p;
                        ap.source = key;
                        return ap;
                    }
                }
            }
        }
        return permission;
        return ap;
    }
    
    protected boolean canAccess(RepositoryModel repository, AccessRestrictionType ifRestriction, AccessPermission requirePermission) {
        if (repository.accessRestriction.atLeast(ifRestriction)) {
            AccessPermission permission = getRepositoryPermission(repository);
            return permission.atLeast(requirePermission);
            RegistrantAccessPermission ap = getRepositoryPermission(repository);
            return ap.permission.atLeast(requirePermission);
        }
        return true;
    }
src/com/gitblit/models/UserModel.java
@@ -140,16 +140,25 @@
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
        for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
            String registrant = entry.getKey();
            String source = null;
            boolean editable = true;
            PermissionType pType = PermissionType.EXPLICIT;
            if (isMyPersonalRepository(registrant)) {
            if (canAdmin()) {
                pType = PermissionType.ADMINISTRATOR;
                editable = false;
            } else if (isMyPersonalRepository(registrant)) {
                pType = PermissionType.OWNER;
                editable = false;
            } else if (StringUtils.findInvalidCharacter(registrant) != null) {
                // a regex will have at least 1 invalid character
                pType = PermissionType.REGEX;
                source = registrant;
            }
            list.add(new RegistrantAccessPermission(registrant, entry.getValue(), pType, RegistrantType.REPOSITORY, editable));
            if (AccessPermission.MISSING.equals(entry.getValue())) {
                // repository can not be found, permission is not editable
                editable = false;
            }
            list.add(new RegistrantAccessPermission(registrant, entry.getValue(), pType, RegistrantType.REPOSITORY, source, editable));
        }
        Collections.sort(list);
        return list;
@@ -194,6 +203,24 @@
    }
    
    /**
     * Returns true if the user's team memberships specify an access permission for
     * this repository.
     *
     * @param name
     * @return if the user's team memberships specifi an access permission
     */
    public boolean hasTeamRepositoryPermission(String name) {
        if (teams != null) {
            for (TeamModel team : teams) {
                if (team.hasRepositoryPermission(name)) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * Adds a repository permission to the team.
     * <p>
     * Role may be formatted as:
@@ -220,23 +247,52 @@
        permissions.put(repository.toLowerCase(), permission);
    }
    public AccessPermission getRepositoryPermission(RepositoryModel repository) {
        if (canAdmin() || repository.isOwner(username) || repository.isUsersPersonalRepository(username)) {
            return AccessPermission.REWIND;
    public RegistrantAccessPermission getRepositoryPermission(RepositoryModel repository) {
        RegistrantAccessPermission ap = new RegistrantAccessPermission();
        ap.registrant = username;
        ap.registrantType = RegistrantType.USER;
        ap.permission = AccessPermission.NONE;
        ap.isEditable = false;
        // administrator
        if (canAdmin()) {
            ap.permissionType = PermissionType.ADMINISTRATOR;
            ap.permission = AccessPermission.REWIND;
            if (!canAdmin) {
                // administator permission from team membership
                for (TeamModel team : teams) {
                    if (team.canAdmin) {
                        ap.source = team.name;
                        break;
                    }
                }
            }
            return ap;
        }
        // repository owner - either specified owner or personal repository
        if (repository.isOwner(username) || repository.isUsersPersonalRepository(username)) {
            ap.permissionType = PermissionType.OWNER;
            ap.permission = AccessPermission.REWIND;
            return ap;
        }
        if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl) && isAuthenticated) {
            // AUTHENTICATED is a shortcut for authorizing all logged-in users RW access
            return AccessPermission.REWIND;
            ap.permission = AccessPermission.REWIND;
            return ap;
        }
        
        // explicit user permission OR user regex match is used
        // if that fails, then the best team permission is used
        AccessPermission permission = AccessPermission.NONE;
        if (permissions.containsKey(repository.name.toLowerCase())) {
            // exact repository permission specified, use it
            AccessPermission p = permissions.get(repository.name.toLowerCase());
            if (p != null) {
                return p;
                ap.permissionType = PermissionType.EXPLICIT;
                ap.permission = p;
                ap.isEditable = true;
                return ap;
            }
        } else {
            // search for case-insensitive regex permission match
@@ -245,29 +301,33 @@
                    AccessPermission p = permissions.get(key);
                    if (p != null) {
                        // take first match
                        permission = p;
                        break;
                        ap.permissionType = PermissionType.REGEX;
                        ap.permission = p;
                        ap.source = key;
                        return ap;
                    }
                }
            }
        }
        
        if (AccessPermission.NONE.equals(permission)) {
            for (TeamModel team : teams) {
                AccessPermission p = team.getRepositoryPermission(repository);
                if (p.exceeds(permission)) {
                    // use highest team permission
                    permission = p;
                }
        // try to find a team match
        for (TeamModel team : teams) {
            RegistrantAccessPermission p = team.getRepositoryPermission(repository);
            if (p.permission.exceeds(ap.permission)) {
                // use highest team permission
                ap.permission = p.permission;
                ap.source = team.name;
                ap.permissionType = PermissionType.TEAM;
            }
        }
        return permission;
        }
        return ap;
    }
    
    protected boolean canAccess(RepositoryModel repository, AccessRestrictionType ifRestriction, AccessPermission requirePermission) {
        if (repository.accessRestriction.atLeast(ifRestriction)) {
            AccessPermission permission = getRepositoryPermission(repository);
            return permission.atLeast(requirePermission);
            RegistrantAccessPermission ap = getRepositoryPermission(repository);
            return ap.permission.atLeast(requirePermission);
        }
        return true;
    }
src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -357,11 +357,15 @@
gb.deletePermission = {0} (push, ref creation+deletion)
gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
gb.permission = permission
gb.regexPermission = this permission is set from a regular expression
gb.regexPermission = this permission is set from regular expression \"{0}\"
gb.accessDenied = access denied
gb.busyCollectingGarbage = sorry, Gitblit is busy collecting garbage in {0}
gb.gcPeriod = GC period
gb.gcPeriodDescription = duration between garbage collections
gb.gcThreshold = GC threshold
gb.gcThresholdDescription = minimum total size of loose objects to trigger early garbage collection
gb.ownerPermission = repository owner
gb.ownerPermission = repository owner
gb.administrator = admin
gb.administratorPermission = Gitblit administrator
gb.team = team
gb.teamPermission = permission set by \"{0}\" team membership
src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
@@ -9,13 +9,13 @@
    <div wicket:id="permissionRow">
        <div style="padding-top:10px;border-left:1px solid #ccc;border-right:1px solid #ccc;" class="row-fluid">
            <div style="padding-top:5px;padding-left:5px" class="span6"><span wicket:id="registrant"></span></div><div style="padding-top:5px;padding-right:5px;text-align:right;" class="span2"><span class="label" wicket:id="pType">[permission type]</span></div> <select class="input-medium" wicket:id="permission"></select>
            <div style="padding-top:5px;padding-left:5px" class="span6"><span wicket:id="registrant"></span></div><div style="padding-top:5px;padding-right:5px;text-align:right;" class="span3"><span class="label" wicket:id="pType">[permission type]</span></div> <select class="input-medium" wicket:id="permission"></select>
        </div>
    </div>
    <div style="padding-top:15px;" class="row-fluid">
        <form style="padding: 20px 40px;" class="well form-inline" wicket:id="addPermissionForm">
            <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"/>
            <select class="input-xlarge" wicket:id="registrant"></select> <select class="input-large" 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
@@ -15,6 +15,7 @@
 */
package com.gitblit.wicket.panels;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
@@ -109,7 +110,7 @@
                    }
                    Fragment userFragment = new Fragment("registrant", "userRegistrant", RegistrantPermissionsPanel.this);
                    userFragment.add(new GravatarImage("userAvatar", ident, 16, false));
                    userFragment.add(new GravatarImage("userAvatar", ident, 20, false));
                    userFragment.add(new Label("userName", entry.registrant));                    
                    item.add(userFragment);                    
                } else {
@@ -119,14 +120,30 @@
                    item.add(teamFragment);
                }
                switch (entry.permissionType) {
                case ADMINISTRATOR:
                    Label administrator = new Label("pType", entry.source == null ? getString("gb.administrator") : entry.source);
                    WicketUtils.setHtmlTooltip(administrator, getString("gb.administratorPermission"));
                    WicketUtils.setCssClass(administrator, "label label-inverse");
                    item.add(administrator);
                    break;
                case OWNER:
                    Label owner = new Label("pType", "owner");
                    Label owner = new Label("pType", getString("gb.owner"));
                    WicketUtils.setHtmlTooltip(owner, getString("gb.ownerPermission"));
                    WicketUtils.setCssClass(owner, "label label-info");
                    item.add(owner);
                    break;
                case TEAM:
                    Label team = new Label("pType", entry.source == null ? getString("gb.team") : entry.source);
                    WicketUtils.setHtmlTooltip(team, MessageFormat.format(getString("gb.teamPermission"), entry.source));
                    WicketUtils.setCssClass(team, "label label-success");
                    item.add(team);
                    break;
                case REGEX:
                    Label regex = new Label("pType", "regex");
                    WicketUtils.setHtmlTooltip(regex, getString("gb.regexPermission"));
                    if (!StringUtils.isEmpty(entry.source)) {
                        WicketUtils.setHtmlTooltip(regex, MessageFormat.format(getString("gb.regexPermission"), entry.source));
                    }
                    WicketUtils.setCssClass(regex, "label");
                    item.add(regex);
                    break;
                default:
@@ -165,9 +182,15 @@
        final List<String> registrants = new ArrayList<String>(allRegistrants);
        for (RegistrantAccessPermission rp : permissions) {
            if (rp.isEditable) {
                // only remove editable duplicates
                // remove editable duplicates
                // this allows for specifying an explicit permission
                registrants.remove(rp.registrant);
            } else if (rp.isAdmin()) {
                // administrators can not have their permission changed
                registrants.remove(rp.registrant);
            } else if (rp.isOwner()) {
                // owners can not have their permission changed
                registrants.remove(rp.registrant);
            }
        }
tests/com/gitblit/tests/RpcTests.java
@@ -200,7 +200,7 @@
        List<RegistrantAccessPermission> permissions = RpcUtils.getRepositoryMemberPermissions(retrievedRepository, url, account,
                password.toCharArray());
        assertEquals("Membership permissions is not empty!", 0, permissions.size());
        permissions.add(new RegistrantAccessPermission(testMember.username, AccessPermission.PUSH, PermissionType.EXPLICIT, RegistrantType.USER, true));
        permissions.add(new RegistrantAccessPermission(testMember.username, AccessPermission.PUSH, PermissionType.EXPLICIT, RegistrantType.USER, null, true));
        assertTrue(
                "Failed to set member permissions!",
                RpcUtils.setRepositoryMemberPermissions(retrievedRepository, permissions, url, account,
@@ -289,7 +289,7 @@
        // set no teams
        List<RegistrantAccessPermission> permissions = new ArrayList<RegistrantAccessPermission>();
        for (String team : helloworldTeams) {
            permissions.add(new RegistrantAccessPermission(team, AccessPermission.NONE, PermissionType.EXPLICIT, RegistrantType.TEAM, true));
            permissions.add(new RegistrantAccessPermission(team, AccessPermission.NONE, PermissionType.EXPLICIT, RegistrantType.TEAM, null, true));
        }
        assertTrue(RpcUtils.setRepositoryTeamPermissions(helloworld, permissions, url, account,
                password.toCharArray()));