James Moger
2012-10-29 092f0a62302e87f44403ba24fc519c65534dbfff
Stabilizing and polishing permissions ui.  Still in-progress.
23 files modified
609 ■■■■ changed files
resources/gitblit.css 6 ●●●●● patch | view | raw | blame | history
src/com/gitblit/Constants.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/GitBlit.java 47 ●●●● patch | view | raw | blame | history
src/com/gitblit/client/EditRepositoryDialog.java 63 ●●●●● patch | view | raw | blame | history
src/com/gitblit/client/EditTeamDialog.java 8 ●●●●● patch | view | raw | blame | history
src/com/gitblit/client/EditUserDialog.java 16 ●●●●● patch | view | raw | blame | history
src/com/gitblit/client/GitblitClient.java 64 ●●●● patch | view | raw | blame | history
src/com/gitblit/client/RegistrantPermissionsPanel.java 52 ●●●● patch | view | raw | blame | history
src/com/gitblit/client/RegistrantPermissionsTableModel.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/client/UsersPanel.java 17 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/RegistrantAccessPermission.java 36 ●●●● patch | view | raw | blame | history
src/com/gitblit/models/TeamModel.java 10 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/UserModel.java 18 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/GitBlitWebApp.properties 5 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/BasePage.java 16 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditRepositoryPage.html 7 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditRepositoryPage.java 125 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditTeamPage.java 8 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditUserPage.java 20 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/RootSubPage.java 19 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java 57 ●●●● patch | view | raw | blame | history
tests/com/gitblit/tests/RpcTests.java 5 ●●●●● patch | view | raw | blame | history
resources/gitblit.css
@@ -188,6 +188,12 @@
    vertical-align: middle;
}
span.authorizationControl label {
    display: inline;
    color: #777;
    padding:5px 0px 5px 10px;
}
div.page_footer {
    clear: both;
    height: 17px;
src/com/gitblit/Constants.java
@@ -386,6 +386,10 @@
        REPOSITORY, USER, TEAM;
    }
    
    public static enum PermissionType {
        EXPLICIT, OWNER, REGEX;
    }
    public static enum GCStatus {
        READY, COLLECTING;
        
src/com/gitblit/GitBlit.java
@@ -79,6 +79,7 @@
import com.gitblit.Constants.FederationRequest;
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;
@@ -670,14 +671,35 @@
     * @return a list of User-AccessPermission tuples
     */
    public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
        List<RegistrantAccessPermission> permissions = new ArrayList<RegistrantAccessPermission>();
        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));
            }
        }
        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);
            boolean isExplicit = model.hasExplicitRepositoryPermission(repository.name);
            permissions.add(new RegistrantAccessPermission(user, ap, isExplicit, RegistrantType.USER));
            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;
        }
        return permissions;
            permissions.add(new RegistrantAccessPermission(user, ap, pType, RegistrantType.USER, editable));
        }
        return new ArrayList<RegistrantAccessPermission>(permissions);
    }
    
    /**
@@ -690,8 +712,8 @@
    public boolean setUserAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
        List<UserModel> users = new ArrayList<UserModel>();
        for (RegistrantAccessPermission up : permissions) {
            if (up.isExplicit) {
                // only set explicitly defined permissions
            if (up.isEditable) {
                // only set editable defined permissions
                UserModel user = userService.getUserModel(up.registrant);
                user.setRepositoryPermission(repository.name, up.permission);
                users.add(user);
@@ -811,8 +833,13 @@
        for (String team : userService.getTeamnamesForRepositoryRole(repository.name)) {
            TeamModel model = userService.getTeamModel(team);
            AccessPermission ap = model.getRepositoryPermission(repository);
            boolean isExplicit = model.hasExplicitRepositoryPermission(repository.name);
            permissions.add(new RegistrantAccessPermission(team, ap, isExplicit, RegistrantType.TEAM));
            PermissionType pType = PermissionType.REGEX;
            boolean editable = false;
            if (model.hasExplicitRepositoryPermission(repository.name)) {
                pType = PermissionType.EXPLICIT;
                editable = true;
            }
            permissions.add(new RegistrantAccessPermission(team, ap, pType, RegistrantType.TEAM, editable));
        }
        return permissions;
    }
@@ -827,7 +854,7 @@
    public boolean setTeamAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
        List<TeamModel> teams = new ArrayList<TeamModel>();
        for (RegistrantAccessPermission tp : permissions) {
            if (tp.isExplicit) {
            if (tp.isEditable) {
                // only set explicitly defined access permissions
                TeamModel team = userService.getTeamModel(tp.registrant);
                team.setRepositoryPermission(repository.name, tp.permission);
@@ -1870,7 +1897,9 @@
        config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFederated", repository.isFederated);
        config.setString(Constants.CONFIG_GITBLIT, null, "gcThreshold", repository.gcThreshold);
        config.setString(Constants.CONFIG_GITBLIT, null, "gcPeriod", repository.gcPeriod);
        if (repository.lastGC != null) {
        config.setString(Constants.CONFIG_GITBLIT, null, "lastGC", new SimpleDateFormat(Constants.ISO8601).format(repository.lastGC));
        }
        updateList(config, "federationSets", repository.federationSets);
        updateList(config, "preReceiveScript", repository.preReceiveScripts);
src/com/gitblit/client/EditRepositoryDialog.java
@@ -24,6 +24,8 @@
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.text.MessageFormat;
import java.util.ArrayList;
@@ -37,6 +39,7 @@
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
@@ -59,6 +62,7 @@
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
import com.gitblit.utils.ArrayUtils;
@@ -218,13 +222,41 @@
        accessRestriction = new JComboBox(AccessRestrictionType.values());
        accessRestriction.setRenderer(new AccessRestrictionRenderer());
        accessRestriction.setSelectedItem(anRepository.accessRestriction);
        accessRestriction.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    AccessRestrictionType art = (AccessRestrictionType) accessRestriction.getSelectedItem();
                    EditRepositoryDialog.this.setupAccessPermissions(art);
                }
            }
        });
        
        boolean authenticated = anRepository.authorizationControl != null 
                && AuthorizationControl.AUTHENTICATED.equals(anRepository.authorizationControl);
        allowAuthenticated = new JRadioButton(Translation.get("gb.allowAuthenticatedDescription"));
        allowAuthenticated.setSelected(authenticated);
        allowAuthenticated.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    usersPalette.setEnabled(false);
                    teamsPalette.setEnabled(false);
                }
            }
        });
        allowNamed = new JRadioButton(Translation.get("gb.allowNamedDescription"));
        allowNamed.setSelected(!authenticated);
        allowNamed.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    usersPalette.setEnabled(true);
                    teamsPalette.setEnabled(true);
                }
            }
        });
        
        ButtonGroup group = new ButtonGroup();
        group.add(allowAuthenticated);
@@ -281,7 +313,7 @@
        clonePushPanel
        .add(newFieldPanel(Translation.get("gb.verifyCommitter"), verifyCommitter));
        usersPalette = new RegistrantPermissionsPanel();
        usersPalette = new RegistrantPermissionsPanel(RegistrantType.USER);
        JPanel northAccessPanel = new JPanel(new BorderLayout(5, 5));
        northAccessPanel.add(newFieldPanel(Translation.get("gb.accessRestriction"),
                accessRestriction), BorderLayout.NORTH);
@@ -294,7 +326,7 @@
        accessPanel.add(newFieldPanel(Translation.get("gb.userPermissions"),
                        usersPalette), BorderLayout.CENTER);
        teamsPalette = new RegistrantPermissionsPanel();
        teamsPalette = new RegistrantPermissionsPanel(RegistrantType.TEAM);
        JPanel teamsPanel = new JPanel(new BorderLayout(5, 5));
        teamsPanel.add(
                newFieldPanel(Translation.get("gb.teamPermissions"),
@@ -349,6 +381,8 @@
        panel.addTab(Translation.get("gb.customFields"), customFieldsScrollPane);
        
        setupAccessPermissions(anRepository.accessRestriction);
        JButton createButton = new JButton(Translation.get("gb.save"));
        createButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
@@ -402,6 +436,25 @@
        panel.add(fieldLabel);
        panel.add(comp);
        return panel;
    }
    private void setupAccessPermissions(AccessRestrictionType art) {
        if (AccessRestrictionType.NONE.equals(art)) {
            usersPalette.setEnabled(false);
            teamsPalette.setEnabled(false);
            allowAuthenticated.setEnabled(false);
            allowNamed.setEnabled(false);
        } else {
            allowAuthenticated.setEnabled(true);
            allowNamed.setEnabled(true);
            if (allowNamed.isSelected()) {
                usersPalette.setEnabled(true);
                teamsPalette.setEnabled(true);
            }
        }
    }
    private boolean validateFields() {
@@ -538,6 +591,7 @@
    
    public void setAccessRestriction(AccessRestrictionType restriction) {
        this.accessRestriction.setSelectedItem(restriction);
        setupAccessPermissions(restriction);
    }
    public void setAuthorizationControl(AuthorizationControl authorization) {
@@ -659,14 +713,15 @@
     * restriction.
     * 
     */
    private class AccessRestrictionRenderer extends JLabel implements
            ListCellRenderer {
    private class AccessRestrictionRenderer extends DefaultListCellRenderer {
        private static final long serialVersionUID = 1L;
        @Override
        public Component getListCellRendererComponent(JList list, Object value,
                int index, boolean isSelected, boolean cellHasFocus) {
            super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            if (value instanceof AccessRestrictionType) {
                AccessRestrictionType restriction = (AccessRestrictionType) value;
                switch (restriction) {
src/com/gitblit/client/EditTeamDialog.java
@@ -45,11 +45,12 @@
import javax.swing.KeyStroke;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.ServerSettings;
import com.gitblit.models.TeamModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
public class EditTeamDialog extends JDialog {
@@ -140,7 +141,7 @@
        fieldsPanel.add(newFieldPanel(Translation.get("gb.mailingLists"), mailingListsField));
        final Insets _insets = new Insets(5, 5, 5, 5);
        repositoryPalette = new RegistrantPermissionsPanel();
        repositoryPalette = new RegistrantPermissionsPanel(RegistrantType.REPOSITORY);
        userPalette = new JPalette<String>();
        userPalette.setEnabled(settings.supportsTeamMembershipChanges);
        
@@ -311,7 +312,8 @@
    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)) {
            if (repo.accessRestriction.exceeds(AccessRestrictionType.NONE)
                    && repo.authorizationControl.equals(AuthorizationControl.NAMED)) {
                restricted.add(repo.name);
            }
        }
src/com/gitblit/client/EditUserDialog.java
@@ -46,6 +46,8 @@
import javax.swing.KeyStroke;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.Keys;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
@@ -158,7 +160,7 @@
                notFederatedCheckbox));
        final Insets _insets = new Insets(5, 5, 5, 5);
        repositoryPalette = new RegistrantPermissionsPanel();
        repositoryPalette = new RegistrantPermissionsPanel(RegistrantType.REPOSITORY);
        teamsPalette = new JPalette<TeamModel>();
        teamsPalette.setEnabled(settings.supportsTeamMembershipChanges);
@@ -343,8 +345,12 @@
    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)) {
            // exclude Owner or personal repositories
            if (!repo.isOwner(username) && !repo.isUsersPersonalRepository(username)) {
                if (repo.accessRestriction.exceeds(AccessRestrictionType.NONE)
                        && repo.authorizationControl.equals(AuthorizationControl.NAMED)) {
                restricted.add(repo.name);
                }
            }
        }
        StringUtils.sortRepositorynames(restricted);
@@ -356,15 +362,15 @@
        list.add("[^~].*");
        String lastProject = null;
        for (String repo : restricted) {
            String projectPath = StringUtils.getFirstPathElement(repo);
            String projectPath = StringUtils.getFirstPathElement(repo).toLowerCase();
            if (lastProject == null || !lastProject.equalsIgnoreCase(projectPath)) {
                lastProject = projectPath;
                if (!StringUtils.isEmpty(projectPath)) {
                    // regex for all repositories within a project
                    list.add(projectPath + "/.*");
                }
                list.add(repo);
            }
            list.add(repo);
        }
        // remove repositories for which user already has a permission
@@ -372,7 +378,7 @@
            permissions = new ArrayList<RegistrantAccessPermission>();
        } else {
            for (RegistrantAccessPermission rp : permissions) {
                list.remove(rp.registrant);
                list.remove(rp.registrant.toLowerCase());
            }
        }
        repositoryPalette.setObjects(list, permissions);
src/com/gitblit/client/GitblitClient.java
@@ -31,6 +31,7 @@
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;
@@ -340,6 +341,7 @@
        List<UserModel> users = RpcUtils.getUsers(url, account, password);
        allUsers.clear();
        allUsers.addAll(users);
        Collections.sort(users);
        return allUsers;
    }
@@ -347,6 +349,7 @@
        List<TeamModel> teams = RpcUtils.getTeams(url, account, password);
        allTeams.clear();
        allTeams.addAll(teams);
        Collections.sort(teams);
        return allTeams;
    }
@@ -476,6 +479,15 @@
        return allUsers;
    }
    public UserModel getUser(String username) {
        for (UserModel user : getUsers()) {
            if (user.username.equalsIgnoreCase(username)) {
                return user;
            }
        }
        return null;
    }
    public List<String> getUsernames() {
        List<String> usernames = new ArrayList<String>();
        for (UserModel user : this.allUsers) {
@@ -496,15 +508,38 @@
    }
    
    public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
        for (UserModel user : allUsers) {
        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));
            }
        }
        for (UserModel user : getUsers()) {
            if (user.hasRepositoryPermission(repository.name)) {
                AccessPermission ap = user.getRepositoryPermission(repository);
                boolean isExplicit = user.hasExplicitRepositoryPermission(repository.name);
                list.add(new RegistrantAccessPermission(user.username, ap, isExplicit, RegistrantType.USER));
                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));
            }
        }
        return list;
        List<RegistrantAccessPermission> raps = new ArrayList<RegistrantAccessPermission>(list);
        Collections.sort(raps);
        return raps;
    }
    public boolean setUserAccessPermissions(RepositoryModel repository, List<RegistrantAccessPermission> permissions) throws IOException {
@@ -539,10 +574,16 @@
        for (TeamModel team : allTeams) {
            if (team.hasRepositoryPermission(repository.name)) {
                AccessPermission ap = team.getRepositoryPermission(repository);
                boolean isExplicit = team.hasExplicitRepositoryPermission(repository.name);
                list.add(new RegistrantAccessPermission(team.name, ap, isExplicit, RegistrantType.TEAM));
                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));
            }
        }
        Collections.sort(list);
        return list;
    }
@@ -567,6 +608,15 @@
        return allRepositories;
    }
    public RepositoryModel getRepository(String name) {
        for (RepositoryModel repository : allRepositories) {
            if (repository.name.equalsIgnoreCase(name)) {
                return repository;
            }
        }
        return null;
    }
    public boolean createRepository(RepositoryModel repository, List<RegistrantAccessPermission> userPermissions)
            throws IOException {
        return createRepository(repository, userPermissions, null);
src/com/gitblit/client/RegistrantPermissionsPanel.java
@@ -33,7 +33,10 @@
import javax.swing.table.DefaultTableCellRenderer;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.utils.StringUtils;
public class RegistrantPermissionsPanel extends JPanel {
@@ -53,16 +56,19 @@
    private JPanel addPanel;
    public RegistrantPermissionsPanel() {
    public RegistrantPermissionsPanel(final RegistrantType registrantType) {
        super(new BorderLayout(5, 5));
        tableModel = new RegistrantPermissionsTableModel();
        permissionsTable = new JTable(tableModel);
        permissionsTable = Utils.newTable(tableModel, Utils.DATE_FORMAT);
        permissionsTable.setModel(tableModel);
        permissionsTable.setPreferredScrollableViewportSize(new Dimension(400, 150));
        JScrollPane jsp = new JScrollPane(permissionsTable);
        add(jsp, BorderLayout.CENTER);
        
        permissionsTable.getColumnModel().getColumn(RegistrantPermissionsTableModel.Columns.Registrant.ordinal())
        .setCellRenderer(new NameRenderer());
        permissionsTable.getColumnModel().getColumn(RegistrantPermissionsTableModel.Columns.Type.ordinal())
                .setCellRenderer(new RegexRenderer());
                .setCellRenderer(new PermissionTypeRenderer());
        permissionsTable.getColumnModel().getColumn(RegistrantPermissionsTableModel.Columns.Permission.ordinal())
        .setCellEditor(new AccessPermissionEditor());
        
@@ -79,9 +85,15 @@
                    return;
                }
                
                RegistrantAccessPermission rp = new RegistrantAccessPermission();
                RegistrantAccessPermission rp = new RegistrantAccessPermission(registrantType);
                rp.registrant = registrantSelector.getSelectedItem().toString();
                rp.permission = (AccessPermission) permissionSelector.getSelectedItem();
                if (StringUtils.findInvalidCharacter(rp.registrant) != null) {
                    rp.permissionType = PermissionType.REGEX;
                } else {
                    rp.permissionType = PermissionType.EXPLICIT;
                }
                tableModel.permissions.add(rp);
                
                registrantModel.removeElement(rp.registrant);
@@ -103,7 +115,10 @@
    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        permissionsTable.setEnabled(false);
        permissionsTable.setEnabled(enabled);
        registrantSelector.setEnabled(enabled);
        permissionSelector.setEnabled(enabled);
        addButton.setEnabled(enabled);
    }
    public void setObjects(List<String> registrants, List<RegistrantAccessPermission> permissions) {
@@ -117,7 +132,11 @@
            permissions = new ArrayList<RegistrantAccessPermission>();
        }
        for (RegistrantAccessPermission rp : permissions) {
            if (rp.isEditable) {
                // only remove editable duplicates
                // this allows for specifying an explicit permission
            filtered.remove(rp.registrant);
            }
        }
        for (String registrant : filtered) {
            registrantModel.addElement(registrant);
@@ -142,26 +161,31 @@
        }
    }
    
    private class RegexRenderer extends DefaultTableCellRenderer {
    private class PermissionTypeRenderer extends DefaultTableCellRenderer {
        private static final long serialVersionUID = 1L;
        public RegexRenderer() {
        public PermissionTypeRenderer() {
            super();
            setHorizontalAlignment(SwingConstants.CENTER);
        }
        @Override
        protected void setValue(Object value) {
            boolean isExplicit = (Boolean) value;
            if (isExplicit) {
                // explicit permission
                setText("");
                setToolTipText(null);
            } else {
                // regex matched permission
            PermissionType pType = (PermissionType) value;
            switch (pType) {
            case OWNER:
                setText("owner");
                setToolTipText(Translation.get("gb.ownerPermission"));
                break;
            case REGEX:
                setText("regex");
                setToolTipText(Translation.get("gb.regexPermission"));
                break;
            default:
                setText("");
                setToolTipText(null);
                break;
            }
        }
    }
src/com/gitblit/client/RegistrantPermissionsTableModel.java
@@ -104,7 +104,7 @@
            // and therefore can not be directly manipulated unless the current
            // object is the source of the regex (i.e. a user or team with explicit
            // regex definition)
            return permissions.get(rowIndex).isExplicit;
            return permissions.get(rowIndex).isEditable;
        }
        return false;
    }
@@ -117,7 +117,7 @@
        case Registrant:
            return rp.registrant;
        case Type:
            return rp.isExplicit;
            return rp.permissionType;
        case Permission:
            return rp.permission;
        }
src/com/gitblit/client/UsersPanel.java
@@ -40,7 +40,9 @@
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableRowSorter;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RpcRequest;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
@@ -309,6 +311,21 @@
                gitblit.getSettings());
        dialog.setLocationRelativeTo(UsersPanel.this);
        dialog.setUsers(gitblit.getUsers());
        List<RegistrantAccessPermission> permissions = user.getRepositoryPermissions();
        for (RegistrantAccessPermission permission : permissions) {
            if (permission.isEditable && PermissionType.EXPLICIT.equals(permission.permissionType)) {
                // Ensure this is NOT an owner permission - which is non-editable
                // We don't know this from within the usermodel, ownership is a
                // property of a repository.
                boolean isOwner = gitblit.getRepository(permission.registrant).isOwner(user.username);
                if (isOwner) {
                    permission.permissionType = PermissionType.OWNER;
                    permission.isEditable = false;
                }
            }
        }
        dialog.setRepositories(gitblit.getRepositories(), user.getRepositoryPermissions());
        dialog.setTeams(gitblit.getTeams(), user.teams == null ? null : new ArrayList<TeamModel>(
                user.teams));
src/com/gitblit/models/RegistrantAccessPermission.java
@@ -18,6 +18,7 @@
import java.io.Serializable;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.utils.StringUtils;
@@ -32,23 +33,27 @@
    public String registrant;
    public AccessPermission permission;
    public RegistrantType type;
    public boolean isExplicit;
    public RegistrantType registrantType;
    public PermissionType permissionType;
    public boolean isEditable;
    public RegistrantAccessPermission() {
        isExplicit = true;
    public RegistrantAccessPermission(RegistrantType registrantType) {
        this.registrantType = registrantType;
        this.permissionType = PermissionType.EXPLICIT;
        this.isEditable = true;
    }
    
    public RegistrantAccessPermission(String registrant, AccessPermission permission, boolean isExplicit, RegistrantType type) {
    public RegistrantAccessPermission(String registrant, AccessPermission permission, PermissionType permissionType, RegistrantType registrantType, boolean isEditable) {
        this.registrant = registrant;
        this.permission = permission;
        this.isExplicit = isExplicit;
        this.type = type;
        this.permissionType = permissionType;
        this.registrantType = registrantType;
        this.isEditable = isEditable;
    }
    
    @Override
    public int compareTo(RegistrantAccessPermission p) {
        switch (type) {
        switch (registrantType) {
        case REPOSITORY:
            return StringUtils.compareRepositoryNames(registrant, p.registrant);
        default:
@@ -57,6 +62,21 @@
    }
    
    @Override
    public int hashCode() {
        return registrant.hashCode();
    }
    @Override
    public boolean equals(Object o) {
        if (o instanceof RegistrantAccessPermission) {
            RegistrantAccessPermission p = (RegistrantAccessPermission) o;
            return registrant.equals(p.registrant);
        }
        return false;
    }
    @Override
    public String toString() {
        return permission.asRole(registrant);
    }
src/com/gitblit/models/TeamModel.java
@@ -27,6 +27,7 @@
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.Constants.Unused;
import com.gitblit.utils.StringUtils;
@@ -98,7 +99,14 @@
    public List<RegistrantAccessPermission> getRepositoryPermissions() {
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
        for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
            list.add(new RegistrantAccessPermission(entry.getKey(), entry.getValue(), true, RegistrantType.REPOSITORY));
            String registrant = entry.getKey();
            boolean editable = true;
            PermissionType pType = PermissionType.EXPLICIT;
            if (StringUtils.findInvalidCharacter(registrant) != null) {
                // a regex will have at least 1 invalid character
                pType = PermissionType.REGEX;
            }
            list.add(new RegistrantAccessPermission(registrant, entry.getValue(), pType, RegistrantType.REPOSITORY, editable));
        }
        Collections.sort(list);
        return list;
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.PermissionType;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.Constants.Unused;
import com.gitblit.utils.ArrayUtils;
@@ -137,7 +138,17 @@
    public List<RegistrantAccessPermission> getRepositoryPermissions() {
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
        for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
            list.add(new RegistrantAccessPermission(entry.getKey(), entry.getValue(), true, RegistrantType.REPOSITORY));
            String registrant = entry.getKey();
            boolean editable = true;
            PermissionType pType = PermissionType.EXPLICIT;
            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;
            }
            list.add(new RegistrantAccessPermission(registrant, entry.getValue(), pType, RegistrantType.REPOSITORY, editable));
        }
        Collections.sort(list);
        return list;
@@ -494,4 +505,9 @@
        // Default UserModel doesn't implement branch-level security. Other Realms (i.e. Gerrit) may override this method.
        return hasRepositoryPermission(repositoryName);
    }
    public boolean isMyPersonalRepository(String repository) {
        String projectPath = StringUtils.getFirstPathElement(repository);
        return !StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase("~" + username);
    }
}
src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -311,8 +311,8 @@
gb.duration.oneYear = 1 year
gb.duration.years = {0} years
gb.authorizationControl = authorization control
gb.allowAuthenticatedDescription = grant restricted access to all authenticated users
gb.allowNamedDescription = grant restricted access to named users or teams
gb.allowAuthenticatedDescription = grant RW+ permission to all authenticated users
gb.allowNamedDescription = grant fine-grained permissions to named users or teams
gb.markdownFailure = Failed to parse Markdown content!
gb.clearCache = clear cache
gb.projects = projects
@@ -364,3 +364,4 @@
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
src/com/gitblit/wicket/pages/BasePage.java
@@ -55,6 +55,7 @@
import com.gitblit.Constants;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
@@ -256,6 +257,21 @@
        return map;
    }
    protected Map<AuthorizationControl, String> getAuthorizationControls() {
        Map<AuthorizationControl, String> map = new LinkedHashMap<AuthorizationControl, String>();
        for (AuthorizationControl type : AuthorizationControl.values()) {
            switch (type) {
            case AUTHENTICATED:
                map.put(type, getString("gb.allowAuthenticatedDescription"));
                break;
            case NAMED:
                map.put(type, getString("gb.allowNamedDescription"));
                break;
            }
        }
        return map;
    }
    protected TimeZone getTimeZone() {
        return GitBlit.getBoolean(Keys.web.useClientTimezone, false) ? GitBlitWebSession.get()
                .getTimezone() : GitBlit.getTimezone();
src/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -49,12 +49,7 @@
                <tbody class="settings">
                    <tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span4" wicket:id="accessRestriction" tabindex="15" /></td></tr>
                    <tr><th colspan="2"><hr/></th></tr>
                    <tr><th><wicket:message key="gb.authorizationControl"></wicket:message></th><td style="padding:2px;">
                        <wicket:container wicket:id="authorizationControl">
                            <label class="radio"><input type="radio" wicket:id="allowAuthenticated" tabindex="16" /> &nbsp;<span class="help-inline"><wicket:message key="gb.allowAuthenticatedDescription"></wicket:message></span></label>
                            <label class="radio"><input type="radio" wicket:id="allowNamed" tabindex="17" /> &nbsp;<span class="help-inline"><wicket:message key="gb.allowNamedDescription"></wicket:message></span></label>
                        </wicket:container>
                    </td></tr>
                    <tr><th><wicket:message key="gb.authorizationControl"></wicket:message></th><td style="padding:2px;"><span class="authorizationControl" wicket:id="authorizationControl"></span></td></tr>
                    <tr><th colspan="2"><hr/></th></tr>
                    <tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="isFrozen" tabindex="18" /> &nbsp;<span class="help-inline"><wicket:message key="gb.isFrozenDescription"></wicket:message></span></label></td></tr>
                    <tr><th><wicket:message key="gb.allowForks"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="allowForks" tabindex="19" /> &nbsp;<span class="help-inline"><wicket:message key="gb.allowForksDescription"></wicket:message></span></label></td></tr>
src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -27,6 +27,9 @@
import java.util.Set;
import org.apache.wicket.PageParameters;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.extensions.markup.html.form.palette.Palette;
import org.apache.wicket.markup.html.WebMarkupContainer;
@@ -36,8 +39,7 @@
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.IChoiceRenderer;
import org.apache.wicket.markup.html.form.Radio;
import org.apache.wicket.markup.html.form.RadioGroup;
import org.apache.wicket.markup.html.form.RadioChoice;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
@@ -51,6 +53,7 @@
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.GitBlit;
import com.gitblit.GitBlitException;
import com.gitblit.Keys;
@@ -70,6 +73,8 @@
    private final boolean isCreate;
    private boolean isAdmin;
    RepositoryModel repositoryModel;
    private IModel<String> mailingLists;
@@ -97,6 +102,7 @@
        
        setupPage(model);
        setStatelessHint(false);
        setOutputMarkupId(true);
    }
    public EditRepositoryPage(PageParameters params) {
@@ -107,9 +113,12 @@
        RepositoryModel model = GitBlit.self().getRepositoryModel(name);
        setupPage(model);
        setStatelessHint(false);
        setOutputMarkupId(true);
    }
    protected void setupPage(final RepositoryModel repositoryModel) {
    protected void setupPage(RepositoryModel model) {
        this.repositoryModel = model;
        // ensure this user can create or edit this repository
        checkPermissions(repositoryModel);
@@ -145,10 +154,10 @@
        final String oldName = repositoryModel.name;
        RegistrantPermissionsPanel usersPalette = new RegistrantPermissionsPanel("users",
                GitBlit.self().getAllUsernames(), repositoryUsers, getAccessPermissions());
        RegistrantPermissionsPanel teamsPalette = new RegistrantPermissionsPanel("teams",
                GitBlit.self().getAllTeamnames(), repositoryTeams, getAccessPermissions());
        final RegistrantPermissionsPanel usersPalette = new RegistrantPermissionsPanel("users",
                RegistrantType.USER, GitBlit.self().getAllUsernames(), repositoryUsers, getAccessPermissions());
        final RegistrantPermissionsPanel teamsPalette = new RegistrantPermissionsPanel("teams",
                RegistrantType.TEAM, GitBlit.self().getAllTeamnames(), repositoryTeams, getAccessPermissions());
        // indexed local branches palette
        List<String> allLocalBranches = new ArrayList<String>();
@@ -206,9 +215,9 @@
        };
        customFieldsListView.setReuseItems(true);
        CompoundPropertyModel<RepositoryModel> model = new CompoundPropertyModel<RepositoryModel>(
        CompoundPropertyModel<RepositoryModel> rModel = new CompoundPropertyModel<RepositoryModel>(
                repositoryModel);
        Form<RepositoryModel> form = new Form<RepositoryModel>("editForm", model) {
        Form<RepositoryModel> form = new Form<RepositoryModel>("editForm", rModel) {
            private static final long serialVersionUID = 1L;
@@ -366,8 +375,9 @@
        form.add(new DropDownChoice<String>("owner", GitBlit.self().getAllUsernames())
                .setEnabled(GitBlitWebSession.get().canAdmin()));
        form.add(new CheckBox("allowForks"));
        form.add(new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays
                .asList(AccessRestrictionType.values()), new AccessRestrictionRenderer()));
        DropDownChoice<AccessRestrictionType> accessRestriction = new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays
                .asList(AccessRestrictionType.values()), new AccessRestrictionRenderer());
        form.add(accessRestriction);
        form.add(new CheckBox("isFrozen"));
        // TODO enable origin definition
        form.add(new TextField<String>("origin").setEnabled(false/* isCreate */));
@@ -403,12 +413,10 @@
        form.add(new TextField<String>("mailingLists", mailingLists));
        form.add(indexedBranchesPalette);
        
        RadioGroup<AuthorizationControl> group = new RadioGroup<AuthorizationControl>("authorizationControl");
        Radio<AuthorizationControl> allowAuthenticated = new Radio<AuthorizationControl>("allowAuthenticated", new Model<AuthorizationControl>(AuthorizationControl.AUTHENTICATED));
        Radio<AuthorizationControl> allowNamed = new Radio<AuthorizationControl>("allowNamed", new Model<AuthorizationControl>(AuthorizationControl.NAMED));
        group.add(allowAuthenticated);
        group.add(allowNamed);
        form.add(group);
        List<AuthorizationControl> acList = Arrays.asList(AuthorizationControl.values());
        final RadioChoice<AuthorizationControl> authorizationControl = new RadioChoice<Constants.AuthorizationControl>(
                "authorizationControl", acList, new AuthorizationControlRenderer());
        form.add(authorizationControl);
                
        form.add(new CheckBox("verifyCommitter"));
@@ -425,6 +433,68 @@
        WebMarkupContainer customFieldsSection = new WebMarkupContainer("customFieldsSection");
        customFieldsSection.add(customFieldsListView);
        form.add(customFieldsSection.setVisible(!GitBlit.getString(Keys.groovy.customFields, "").isEmpty()));
        // initial enable/disable of permission controls
        if (repositoryModel.accessRestriction.equals(AccessRestrictionType.NONE)) {
            // anonymous everything, disable all controls
            usersPalette.setEnabled(false);
            teamsPalette.setEnabled(false);
            authorizationControl.setEnabled(false);
        } else {
            // authenticated something
            // enable authorization controls
            authorizationControl.setEnabled(true);
            boolean allowFineGrainedControls = repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED);
            usersPalette.setEnabled(allowFineGrainedControls);
            teamsPalette.setEnabled(allowFineGrainedControls);
        }
        accessRestriction.add(new AjaxFormComponentUpdatingBehavior("onchange") {
            private static final long serialVersionUID = 1L;
            protected void onUpdate(AjaxRequestTarget target) {
                // enable/disable permissions panel based on access restriction
                boolean allowAuthorizationControl = repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE);
                authorizationControl.setEnabled(allowAuthorizationControl);
                boolean allowFineGrainedControls = allowAuthorizationControl && repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED);
                usersPalette.setEnabled(allowFineGrainedControls);
                teamsPalette.setEnabled(allowFineGrainedControls);
                if (allowFineGrainedControls) {
                    repositoryModel.authorizationControl = AuthorizationControl.NAMED;
                }
                target.addComponent(authorizationControl);
                target.addComponent(usersPalette);
                target.addComponent(teamsPalette);
            }
        });
        authorizationControl.add(new AjaxFormChoiceComponentUpdatingBehavior() {
            private static final long serialVersionUID = 1L;
            protected void onUpdate(AjaxRequestTarget target) {
                // enable/disable permissions panel based on access restriction
                boolean allowAuthorizationControl = repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE);
                authorizationControl.setEnabled(allowAuthorizationControl);
                boolean allowFineGrainedControls = allowAuthorizationControl && repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED);
                usersPalette.setEnabled(allowFineGrainedControls);
                teamsPalette.setEnabled(allowFineGrainedControls);
                if (allowFineGrainedControls) {
                    repositoryModel.authorizationControl = AuthorizationControl.NAMED;
                }
                target.addComponent(authorizationControl);
                target.addComponent(usersPalette);
                target.addComponent(teamsPalette);
            }
        });
        form.add(new Button("save"));
        Button cancel = new Button("cancel") {
@@ -528,4 +598,25 @@
            return Integer.toString(index);
        }
    }
    private class AuthorizationControlRenderer implements IChoiceRenderer<AuthorizationControl> {
        private static final long serialVersionUID = 1L;
        private final Map<AuthorizationControl, String> map;
        public AuthorizationControlRenderer() {
            map = getAuthorizationControls();
        }
        @Override
        public String getDisplayValue(AuthorizationControl type) {
            return map.get(type);
        }
        @Override
        public String getIdValue(AuthorizationControl type, int index) {
            return Integer.toString(index);
        }
    }
}
src/com/gitblit/wicket/pages/EditTeamPage.java
@@ -38,6 +38,7 @@
import com.gitblit.GitBlit;
import com.gitblit.GitBlitException;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.TeamModel;
import com.gitblit.utils.StringUtils;
@@ -60,6 +61,7 @@
        isCreate = true;
        setupPage(new TeamModel(""));
        setStatelessHint(false);
        setOutputMarkupId(true);
    }
    public EditTeamPage(PageParameters params) {
@@ -70,6 +72,7 @@
        TeamModel model = GitBlit.self().getTeamModel(name);
        setupPage(model);
        setStatelessHint(false);
        setOutputMarkupId(true);
    }
    protected void setupPage(final TeamModel teamModel) {
@@ -81,7 +84,7 @@
        CompoundPropertyModel<TeamModel> model = new CompoundPropertyModel<TeamModel>(teamModel);
        List<String> repos = getAccessRestrictedRepositoryList(true);
        List<String> repos = getAccessRestrictedRepositoryList(true, null);
        List<String> teamUsers = new ArrayList<String>(teamModel.users);
        Collections.sort(teamUsers);
@@ -215,7 +218,8 @@
                : StringUtils.flattenStrings(teamModel.mailingLists, " "));
        form.add(new TextField<String>("mailingLists", mailingLists));
        form.add(new RegistrantPermissionsPanel("repositories", repos, permissions, getAccessPermissions()));
        form.add(new RegistrantPermissionsPanel("repositories", RegistrantType.REPOSITORY,
                repos, permissions, getAccessPermissions()));
        form.add(preReceivePalette);
        form.add(new BulletListPanel("inheritedPreReceive", "inherited", GitBlit.self()
                .getPreReceiveScriptsInherited(null)));
src/com/gitblit/wicket/pages/EditUserPage.java
@@ -37,6 +37,8 @@
import com.gitblit.GitBlit;
import com.gitblit.GitBlitException;
import com.gitblit.Keys;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
@@ -61,6 +63,7 @@
        isCreate = true;
        setupPage(new UserModel(""));
        setStatelessHint(false);
        setOutputMarkupId(true);
    }
    public EditUserPage(PageParameters params) {
@@ -71,6 +74,7 @@
        UserModel model = GitBlit.self().getUserModel(name);
        setupPage(model);
        setStatelessHint(false);
        setOutputMarkupId(true);
    }
    protected void setupPage(final UserModel userModel) {
@@ -85,7 +89,7 @@
        CompoundPropertyModel<UserModel> model = new CompoundPropertyModel<UserModel>(userModel);
        // build list of projects including all repositories wildcards
        List<String> repos = getAccessRestrictedRepositoryList(true);
        List<String> repos = getAccessRestrictedRepositoryList(true, userModel);
        
        List<String> userTeams = new ArrayList<String>();
        for (TeamModel team : userModel.teams) {
@@ -95,6 +99,18 @@
        
        final String oldName = userModel.username;
        final List<RegistrantAccessPermission> permissions = userModel.getRepositoryPermissions();
        for (RegistrantAccessPermission permission : permissions) {
            if (permission.isEditable && PermissionType.EXPLICIT.equals(permission.permissionType)) {
                // Ensure this is NOT an owner permission - which is non-editable
                // We don't know this from within the usermodel, ownership is a
                // property of a repository.
                boolean isOwner = GitBlit.self().getRepositoryModel(permission.registrant).isOwner(oldName);
                if (isOwner) {
                    permission.permissionType = PermissionType.OWNER;
                    permission.isEditable = false;
                }
            }
        }
        final Palette<String> teams = new Palette<String>("teams", new ListModel<String>(
                new ArrayList<String>(userTeams)), new CollectionModel<String>(GitBlit.self()
@@ -228,7 +244,7 @@
        form.add(new CheckBox("canFork"));
        form.add(new CheckBox("canCreate"));
        form.add(new CheckBox("excludeFromFederation"));
        form.add(new RegistrantPermissionsPanel("repositories",    repos, permissions, getAccessPermissions()));
        form.add(new RegistrantPermissionsPanel("repositories",    RegistrantType.REPOSITORY, repos, permissions, getAccessPermissions()));
        form.add(teams.setEnabled(editTeams));
        form.add(new Button("save"));
src/com/gitblit/wicket/pages/RootSubPage.java
@@ -21,9 +21,11 @@
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import com.gitblit.GitBlit;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.GitBlit;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
/**
@@ -52,7 +54,7 @@
        super.setupPage("", pageName);
    }
    
    protected List<String> getAccessRestrictedRepositoryList(boolean includeWildcards) {
    protected List<String> getAccessRestrictedRepositoryList(boolean includeWildcards, UserModel user) {
        // build list of access-restricted projects
        String lastProject = null;
        List<String> repos = new ArrayList<String>();
@@ -62,19 +64,26 @@
            // all repositories excluding personal repositories
            repos.add("[^~].*");
        }
        for (String repo : GitBlit.self().getRepositoryList()) {
            RepositoryModel repositoryModel = GitBlit.self().getRepositoryModel(repo);
            if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
            if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)
                    && repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED)) {
                if (user != null &&
                        (repositoryModel.isOwner(user.username) || repositoryModel.isUsersPersonalRepository(user.username))) {
                    // exclude Owner or personal repositories
                    continue;
                }
                if (includeWildcards) {
                    if (lastProject == null || !lastProject.equalsIgnoreCase(repositoryModel.projectPath)) {
                        lastProject = repositoryModel.projectPath;
                        lastProject = repositoryModel.projectPath.toLowerCase();
                        if (!StringUtils.isEmpty(repositoryModel.projectPath)) {
                            // regex for all repositories within a project
                            repos.add(repositoryModel.projectPath + "/.*");
                        }
                    }
                }
                repos.add(repo);
                repos.add(repo.toLowerCase());
            }
        }
        return repos;
src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
@@ -9,7 +9,7 @@
    <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 label-info" wicket:id="regex">[regex]</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="span2"><span class="label" wicket:id="pType">[permission type]</span></div> <select class="input-medium" wicket:id="permission"></select>
        </div>
    </div>
src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
@@ -36,6 +36,7 @@
import org.apache.wicket.model.IModel;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.utils.DeepCopier;
@@ -52,8 +53,9 @@
    private static final long serialVersionUID = 1L;
    public RegistrantPermissionsPanel(String wicketId, List<String> allRegistrants, final List<RegistrantAccessPermission> permissions, final Map<AccessPermission, String> translations) {
    public RegistrantPermissionsPanel(String wicketId, RegistrantType registrantType, List<String> allRegistrants, final List<RegistrantAccessPermission> permissions, final Map<AccessPermission, String> translations) {
        super(wicketId);
        setOutputMarkupId(true);
        
        // update existing permissions repeater
        RefreshingView<RegistrantAccessPermission> dataView = new RefreshingView<RegistrantAccessPermission>("permissionRow") {
@@ -80,22 +82,40 @@
            
            public void populateItem(final Item<RegistrantAccessPermission> item) {
                final RegistrantAccessPermission entry = item.getModelObject();
                if (RegistrantType.REPOSITORY.equals(entry.type)) {
                    // repository, strip .git and show swatch
                if (RegistrantType.REPOSITORY.equals(entry.registrantType)) {
                    String repoName = StringUtils.stripDotGit(entry.registrant);
                    if (StringUtils.findInvalidCharacter(repoName) == null) {
                        // repository, strip .git and show swatch
                    Label registrant = new Label("registrant", repoName);
                    WicketUtils.setCssClass(registrant, "repositorySwatch");
                    WicketUtils.setCssBackground(registrant, repoName);
                    item.add(registrant);
                } else {
                    item.add(new Label("registrant", entry.registrant));
                        // likely a regex
                        Label label = new Label("registrant", entry.registrant);
                        WicketUtils.setCssStyle(label, "font-weight: bold;");
                        item.add(label);
                }
                if (entry.isExplicit) {
                    item.add(new Label("regex", "").setVisible(false));
                } else {
                    Label regex = new Label("regex", "regex");
                    // user or team
                    Label label = new Label("registrant", entry.registrant);
                    WicketUtils.setCssStyle(label, "font-weight: bold;");
                    item.add(label);
                }
                switch (entry.permissionType) {
                case OWNER:
                    Label owner = new Label("pType", "owner");
                    WicketUtils.setHtmlTooltip(owner, getString("gb.ownerPermission"));
                    item.add(owner);
                    break;
                case REGEX:
                    Label regex = new Label("pType", "regex");
                    WicketUtils.setHtmlTooltip(regex, getString("gb.regexPermission"));
                    item.add(regex);
                    break;
                default:
                    item.add(new Label("pType", "").setVisible(false));
                    break;
                }
                // use ajax to get immediate update of permission level change
@@ -106,8 +126,9 @@
                // only allow changing an explicitly defined permission
                // this is designed to prevent changing a regex permission in
                // a repository
                permissionChoice.setEnabled(entry.isExplicit);
                if (entry.isExplicit) {
                permissionChoice.setEnabled(entry.isEditable);
                permissionChoice.setOutputMarkupId(true);
                if (entry.isEditable) {
                    permissionChoice.add(new AjaxFormComponentUpdatingBehavior("onchange") {
                   
                        private static final long serialVersionUID = 1L;
@@ -127,11 +148,15 @@
        // filter out registrants we already have permissions for
        final List<String> registrants = new ArrayList<String>(allRegistrants);
        for (RegistrantAccessPermission rp : permissions) {
            if (rp.isEditable) {
                // only remove editable duplicates
                // this allows for specifying an explicit permission
            registrants.remove(rp.registrant);
            }
        }
        // add new permission form
        IModel<RegistrantAccessPermission> addPermissionModel = new CompoundPropertyModel<RegistrantAccessPermission>(new RegistrantAccessPermission());
        IModel<RegistrantAccessPermission> addPermissionModel = new CompoundPropertyModel<RegistrantAccessPermission>(new RegistrantAccessPermission(registrantType));
        Form<RegistrantAccessPermission> addPermissionForm = new Form<RegistrantAccessPermission>("addPermissionForm", addPermissionModel);
        addPermissionForm.add(new DropDownChoice<String>("registrant", registrants));
        addPermissionForm.add(new DropDownChoice<AccessPermission>("permission", Arrays
@@ -144,7 +169,11 @@
            protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
                // add permission to our list
                RegistrantAccessPermission rp = (RegistrantAccessPermission) form.getModel().getObject();
                permissions.add(DeepCopier.copy(rp));
                RegistrantAccessPermission copy = DeepCopier.copy(rp);
                if (StringUtils.findInvalidCharacter(copy.registrant) != null) {
                    copy.permissionType = PermissionType.REGEX;
                }
                permissions.add(copy);
                
                // remove registrant from available choices
                registrants.remove(rp.registrant);
@@ -159,6 +188,12 @@
        add(addPermissionForm.setVisible(registrants.size() > 0));
    }
    
    protected boolean getStatelessHint()
    {
        return false;
    }
    private class AccessPermissionRenderer implements IChoiceRenderer<AccessPermission> {
        private static final long serialVersionUID = 1L;
tests/com/gitblit/tests/RpcTests.java
@@ -35,6 +35,7 @@
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.UnauthorizedException;
import com.gitblit.Keys;
@@ -199,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, true, RegistrantType.USER));
        permissions.add(new RegistrantAccessPermission(testMember.username, AccessPermission.PUSH, PermissionType.EXPLICIT, RegistrantType.USER, true));
        assertTrue(
                "Failed to set member permissions!",
                RpcUtils.setRepositoryMemberPermissions(retrievedRepository, permissions, url, account,
@@ -288,7 +289,7 @@
        // set no teams
        List<RegistrantAccessPermission> permissions = new ArrayList<RegistrantAccessPermission>();
        for (String team : helloworldTeams) {
            permissions.add(new RegistrantAccessPermission(team, AccessPermission.NONE, true, RegistrantType.TEAM));
            permissions.add(new RegistrantAccessPermission(team, AccessPermission.NONE, PermissionType.EXPLICIT, RegistrantType.TEAM, true));
        }
        assertTrue(RpcUtils.setRepositoryTeamPermissions(helloworld, permissions, url, account,
                password.toCharArray()));