James Moger
2012-08-02 6adf56bb13227afac2c37871b3443fb5354d132c
Per-repository authorization control: AUTHENTICATED and NAMED (issue-117)
16 files modified
202 ■■■■■ changed files
distrib/gitblit.properties 8 ●●●●● patch | view | raw | blame | history
docs/04_releases.mkd 10 ●●●●● patch | view | raw | blame | history
src/com/gitblit/AuthenticationFilter.java 1 ●●●● patch | view | raw | blame | history
src/com/gitblit/Constants.java 22 ●●●●● patch | view | raw | blame | history
src/com/gitblit/GitBlit.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/GitServlet.java 1 ●●●● patch | view | raw | blame | history
src/com/gitblit/client/EditRepositoryDialog.java 43 ●●●● patch | view | raw | blame | history
src/com/gitblit/client/GitblitClient.java 9 ●●●●● patch | view | raw | blame | history
src/com/gitblit/client/RepositoriesPanel.java 1 ●●●● patch | view | raw | blame | history
src/com/gitblit/models/RepositoryModel.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/models/UserModel.java 8 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/GitBlitWebApp.properties 3 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditRepositoryPage.html 12 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditRepositoryPage.java 13 ●●●●● patch | view | raw | blame | history
tests/com/gitblit/tests/GitServletTest.java 60 ●●●●● patch | view | raw | blame | history
tests/com/gitblit/tests/RpcTests.java 3 ●●●●● patch | view | raw | blame | history
distrib/gitblit.properties
@@ -63,6 +63,14 @@
# SINCE 1.0.0
git.defaultAccessRestriction = NONE
# The default authorization control for new repositories.
# Valid values are AUTHENTICATED and NAMED
#  AUTHENTICATED = any authenticated user is granted restricted access
#  NAMED = only named users/teams are granted restricted access
#
# SINCE 1.0.1
git.defaultAuthorizationControl = NAMED
# Number of bytes of a pack file to load into memory in a single read operation.
# This is the "page size" of the JGit buffer cache, used for all pack access
# operations. All disk IO occurs as single window reads. Setting this too large
docs/04_releases.mkd
@@ -16,14 +16,22 @@
- Fixed Lucene charset encoding bug when reindexing a repository (issue 112)
- Fixed null pointer in LdapUserSerivce if account has a null email address (issue 110)
#### changes
#### additions
- Added a repository setting to control authorization as AUTHENTICATED or NAMED.
NAMED is the original behavior for authorizing against a list of permitted users or permitted teams.
AUTHENTICATED allows restricted access for any authenticated user.
- Added default authorization control setting (AUTHENTICATED or NAMED)
    **New:** *git.defaultAuthorizationControl=NAMED*
- Added setting to control how deep Gitblit will recurse into *git.repositoriesFolder* looking for repositories (issue 103)
    **New:** *git.searchRecursionDepth=-1*  
- Added setting to specify regex exclusions for repositories (issue 103)
    **New:** *git.searchExclusions=*  
- Blob page now supports displaying images (issue 6)
- Non-image binary files can now be downloaded using the RAW link
#### changes
- Updated Polish translation
**1.0.0** *released 2012-07-14*
src/com/gitblit/AuthenticationFilter.java
@@ -170,6 +170,7 @@
        public AuthenticatedRequest(HttpServletRequest req) {
            super(req);
            user = new UserModel("anonymous");
            user.isAuthenticated = false;
        }
        UserModel getUser() {
src/com/gitblit/Constants.java
@@ -109,6 +109,28 @@
            return name();
        }
    }
    /**
     * Enumeration representing the types of authorization control for an
     * access restricted resource.
     */
    public static enum AuthorizationControl {
        AUTHENTICATED, NAMED;
        public static AuthorizationControl fromName(String name) {
            for (AuthorizationControl type : values()) {
                if (type.name().equalsIgnoreCase(name)) {
                    return type;
                }
            }
            return NAMED;
        }
        public String toString() {
            return name();
        }
    }
    /**
     * Enumeration representing the types of federation tokens.
src/com/gitblit/GitBlit.java
@@ -69,6 +69,7 @@
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.FederationRequest;
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.Constants.FederationToken;
@@ -876,6 +877,8 @@
            model.useDocs = getConfig(config, "useDocs", false);
            model.accessRestriction = AccessRestrictionType.fromName(getConfig(config,
                    "accessRestriction", settings.getString(Keys.git.defaultAccessRestriction, null)));
            model.authorizationControl = AuthorizationControl.fromName(getConfig(config,
                    "authorizationControl", settings.getString(Keys.git.defaultAuthorizationControl, null)));
            model.showRemoteBranches = getConfig(config, "showRemoteBranches", false);
            model.isFrozen = getConfig(config, "isFrozen", false);
            model.showReadme = getConfig(config, "showReadme", false);
@@ -1135,6 +1138,7 @@
        config.setBoolean(Constants.CONFIG_GITBLIT, null, "useTickets", repository.useTickets);
        config.setBoolean(Constants.CONFIG_GITBLIT, null, "useDocs", repository.useDocs);
        config.setString(Constants.CONFIG_GITBLIT, null, "accessRestriction", repository.accessRestriction.name());
        config.setString(Constants.CONFIG_GITBLIT, null, "authorizationControl", repository.authorizationControl.name());
        config.setBoolean(Constants.CONFIG_GITBLIT, null, "showRemoteBranches", repository.showRemoteBranches);
        config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFrozen", repository.isFrozen);
        config.setBoolean(Constants.CONFIG_GITBLIT, null, "showReadme", repository.showReadme);
src/com/gitblit/GitServlet.java
@@ -231,6 +231,7 @@
            if (user == null) {
                // anonymous push, create a temporary usermodel
                user = new UserModel(person.getName());
                user.isAuthenticated = false;
            }
            return user;
        }
src/com/gitblit/client/EditRepositoryDialog.java
@@ -35,6 +35,7 @@
import java.util.Set;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.DefaultComboBoxModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
@@ -46,6 +47,7 @@
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JRootPane;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
@@ -55,6 +57,7 @@
import javax.swing.ScrollPaneConstants;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.models.RepositoryModel;
import com.gitblit.utils.ArrayUtils;
@@ -98,6 +101,10 @@
    private JTextField mailingListsField;
    private JComboBox accessRestriction;
    private JRadioButton allowAuthenticated;
    private JRadioButton allowNamed;
    private JComboBox federationStrategy;
@@ -206,6 +213,21 @@
        accessRestriction = new JComboBox(AccessRestrictionType.values());
        accessRestriction.setRenderer(new AccessRestrictionRenderer());
        accessRestriction.setSelectedItem(anRepository.accessRestriction);
        boolean authenticated = anRepository.authorizationControl != null
                && AuthorizationControl.AUTHENTICATED.equals(anRepository.authorizationControl);
        allowAuthenticated = new JRadioButton(Translation.get("gb.allowAuthenticatedDescription"));
        allowAuthenticated.setSelected(authenticated);
        allowNamed = new JRadioButton(Translation.get("gb.allowNamedDescription"));
        allowNamed.setSelected(!authenticated);
        ButtonGroup group = new ButtonGroup();
        group.add(allowAuthenticated);
        group.add(allowNamed);
        JPanel authorizationPanel = new JPanel(new GridLayout(0, 1));
        authorizationPanel.add(allowAuthenticated);
        authorizationPanel.add(allowNamed);
        // federation strategies - remove ORIGIN choice if this repository has
        // no origin.
@@ -246,12 +268,15 @@
                mailingListsField));
        usersPalette = new JPalette<String>();
        JPanel northAccessPanel = new JPanel(new BorderLayout(5, 5));
        northAccessPanel.add(newFieldPanel(Translation.get("gb.accessRestriction"),
                accessRestriction), BorderLayout.NORTH);
        northAccessPanel.add(newFieldPanel(Translation.get("gb.authorizationControl"),
                authorizationPanel), BorderLayout.CENTER);
        JPanel accessPanel = new JPanel(new BorderLayout(5, 5));
        accessPanel.add(
                newFieldPanel(Translation.get("gb.accessRestriction"),
                        accessRestriction), BorderLayout.NORTH);
        accessPanel.add(
                newFieldPanel(Translation.get("gb.permittedUsers"),
        accessPanel.add(northAccessPanel, BorderLayout.NORTH);
        accessPanel.add(newFieldPanel(Translation.get("gb.permittedUsers"),
                        usersPalette), BorderLayout.CENTER);
        teamsPalette = new JPalette<String>();
@@ -463,6 +488,8 @@
        repository.accessRestriction = (AccessRestrictionType) accessRestriction
                .getSelectedItem();
        repository.authorizationControl = allowAuthenticated.isSelected() ?
                AuthorizationControl.AUTHENTICATED : AuthorizationControl.NAMED;
        repository.federationStrategy = (FederationStrategy) federationStrategy
                .getSelectedItem();
@@ -495,6 +522,12 @@
        this.accessRestriction.setSelectedItem(restriction);
    }
    public void setAuthorizationControl(AuthorizationControl authorization) {
        boolean authenticated = authorization != null && AuthorizationControl.AUTHENTICATED.equals(authorization);
        this.allowAuthenticated.setSelected(authenticated);
        this.allowNamed.setSelected(!authenticated);
    }
    public void setUsers(String owner, List<String> all, List<String> selected) {
        ownerField.setModel(new DefaultComboBoxModel(all.toArray()));
        if (!StringUtils.isEmpty(owner)) {
src/com/gitblit/client/GitblitClient.java
@@ -29,6 +29,7 @@
import com.gitblit.Constants;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.GitBlitException.ForbiddenException;
import com.gitblit.GitBlitException.NotAllowedException;
import com.gitblit.GitBlitException.UnauthorizedException;
@@ -195,6 +196,14 @@
        return AccessRestrictionType.fromName(restriction);
    }
    public AuthorizationControl getDefaultAuthorizationControl() {
        String authorization = null;
        if (settings.hasKey(Keys.git.defaultAuthorizationControl)) {
            authorization = settings.get(Keys.git.defaultAuthorizationControl).currentValue;
        }
        return AuthorizationControl.fromName(authorization);
    }
    /**
     * Returns the list of pre-receive scripts the repository inherited from the
     * global settings and team affiliations.
src/com/gitblit/client/RepositoriesPanel.java
@@ -358,6 +358,7 @@
        EditRepositoryDialog dialog = new EditRepositoryDialog(gitblit.getProtocolVersion());
        dialog.setLocationRelativeTo(RepositoriesPanel.this);
        dialog.setAccessRestriction(gitblit.getDefaultAccessRestriction());
        dialog.setAuthorizationControl(gitblit.getDefaultAuthorizationControl());
        dialog.setUsers(null, gitblit.getUsernames(), null);
        dialog.setTeams(gitblit.getTeamnames(), null);
        dialog.setRepositories(gitblit.getRepositories());
src/com/gitblit/models/RepositoryModel.java
@@ -22,6 +22,7 @@
import java.util.Map;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
@@ -47,6 +48,8 @@
    public boolean useTickets;
    public boolean useDocs;
    public AccessRestrictionType accessRestriction;
    public AuthorizationControl authorizationControl;
    public boolean allowAuthenticated;
    public boolean isFrozen;
    public boolean showReadme;
    public FederationStrategy federationStrategy;
@@ -77,6 +80,7 @@
        this.owner = owner;
        this.lastChange = lastchange;
        this.accessRestriction = AccessRestrictionType.NONE;
        this.authorizationControl = AuthorizationControl.NAMED;
        this.federationSets = new ArrayList<String>();
        this.federationStrategy = FederationStrategy.FEDERATE_THIS;        
    }
src/com/gitblit/models/UserModel.java
@@ -20,6 +20,7 @@
import java.util.HashSet;
import java.util.Set;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.utils.StringUtils;
/**
@@ -45,8 +46,12 @@
    public final Set<String> repositories = new HashSet<String>();
    public final Set<TeamModel> teams = new HashSet<TeamModel>();
    // non-persisted fields
    public boolean isAuthenticated;
    public UserModel(String username) {
        this.username = username;
        this.isAuthenticated = true;
    }
    /**
@@ -65,8 +70,9 @@
    public boolean canAccessRepository(RepositoryModel repository) {
        boolean isOwner = !StringUtils.isEmpty(repository.owner)
                && repository.owner.equals(username);
        boolean allowAuthenticated = isAuthenticated && AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl);
        return canAdmin || isOwner || repositories.contains(repository.name.toLowerCase())
                || hasTeamAccess(repository.name);
                || hasTeamAccess(repository.name) || allowAuthenticated;
    }
    public boolean hasTeamAccess(String repositoryName) {
src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -310,3 +310,6 @@
gb.duration.months = {0} months
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
src/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -25,11 +25,19 @@
                <tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="isFrozen" tabindex="12" /> &nbsp;<span class="help-inline"><wicket:message key="gb.isFrozenDescription"></wicket:message></span></label></td></tr>
                <tr><th><wicket:message key="gb.mailingLists"></wicket:message></th><td class="edit"><input class="span8" type="text" wicket:id="mailingLists" size="40" tabindex="13" /></td></tr>
                <tr><td colspan="2" style="padding-top:15px"><h3><wicket:message key="gb.accessPermissions"></wicket:message> &nbsp;<small><wicket:message key="gb.accessPermissionsDescription"></wicket:message></small></h3></td></tr>    
                <tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span4" wicket:id="accessRestriction" tabindex="14" /></td></tr>
                <tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span4" wicket:id="accessRestriction" tabindex="14" /></td></tr>
                <tr><th colspan="2"><hr/></th></tr>
                <tr><th style="vertical-align: top;"><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="15" /> &nbsp;<span class="help-inline"><wicket:message key="gb.allowAuthenticatedDescription"></wicket:message></span></label>
                        <label class="radio"><input type="radio" wicket:id="allowNamed" tabindex="16" /> &nbsp;<span class="help-inline"><wicket:message key="gb.allowNamedDescription"></wicket:message></span></label>
                    </wicket:container>
                </td></tr>
                <tr><th colspan="2"><hr/></th></tr>
                <tr><th style="vertical-align: top;"><wicket:message key="gb.permittedUsers"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr>
                <tr><th style="vertical-align: top;"><wicket:message key="gb.permittedTeams"></wicket:message></th><td style="padding:2px;"><span wicket:id="teams"></span></td></tr>
                <tr><td colspan="2"><h3><wicket:message key="gb.federation"></wicket:message> &nbsp;<small><wicket:message key="gb.federationRepositoryDescription"></wicket:message></small></h3></td></tr>    
                <tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span4" wicket:id="federationStrategy" tabindex="15" /></td></tr>
                <tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span4" wicket:id="federationStrategy" tabindex="17" /></td></tr>
                <tr><th style="vertical-align: top;"><wicket:message key="gb.federationSets"></wicket:message></th><td style="padding:2px;"><span wicket:id="federationSets"></span></td></tr>
                <tr><td colspan="2"><h3><wicket:message key="gb.search"></wicket:message> &nbsp;<small><wicket:message key="gb.indexedBranchesDescription"></wicket:message></small></h3></td></tr>    
                <tr><th style="vertical-align: top;"><wicket:message key="gb.indexedBranches"></wicket:message></th><td style="padding:2px;"><span wicket:id="indexedBranches"></span></td></tr>
src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -36,6 +36,8 @@
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.TextField;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
@@ -47,6 +49,7 @@
import com.gitblit.Constants;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.GitBlit;
import com.gitblit.GitBlitException;
@@ -75,6 +78,8 @@
        RepositoryModel model = new RepositoryModel();
        String restriction = GitBlit.getString(Keys.git.defaultAccessRestriction, null);
        model.accessRestriction = AccessRestrictionType.fromName(restriction);
        String authorization = GitBlit.getString(Keys.git.defaultAuthorizationControl, null);
        model.authorizationControl = AuthorizationControl.fromName(authorization);
        setupPage(model);
    }
@@ -370,6 +375,14 @@
                : StringUtils.flattenStrings(repositoryModel.mailingLists, " "));
        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);
        form.add(usersPalette);
        form.add(teamsPalette);
        form.add(federationSetsPalette);
tests/com/gitblit/tests/GitServletTest.java
@@ -21,8 +21,10 @@
import org.junit.Test;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.GitBlit;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
public class GitServletTest {
@@ -108,6 +110,64 @@
        assertFalse("Bogus login cloned a repository?!", cloned);
    }
    @Test
    public void testUnauthorizedLoginClone() throws Exception {
        // restrict repository access
        RepositoryModel model = GitBlit.self().getRepositoryModel("ticgit.git");
        model.accessRestriction = AccessRestrictionType.CLONE;
        model.authorizationControl = AuthorizationControl.NAMED;
        UserModel user = new UserModel("james");
        user.password = "james";
        GitBlit.self().updateUserModel(user.username, user, true);
        GitBlit.self().updateRepositoryModel(model.name, model, false);
        FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
        // delete any existing working folder
        boolean cloned = false;
        try {
            CloneCommand clone = Git.cloneRepository();
            clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
            clone.setDirectory(ticgit2Folder);
            clone.setBare(false);
            clone.setCloneAllBranches(true);
            clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(user.username, user.password));
            close(clone.call());
            cloned = true;
        } catch (Exception e) {
            // swallow the exception which we expect
        }
        assertFalse("Unauthorized login cloned a repository?!", cloned);
        FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
        // switch to authenticated
        model.authorizationControl = AuthorizationControl.AUTHENTICATED;
        GitBlit.self().updateRepositoryModel(model.name, model, false);
        // try clone again
        cloned = false;
        CloneCommand clone = Git.cloneRepository();
        clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
        clone.setDirectory(ticgit2Folder);
        clone.setBare(false);
        clone.setCloneAllBranches(true);
        clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(user.username, user.password));
        close(clone.call());
        cloned = true;
        assertTrue("Authenticated login could not clone!", cloned);
        FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
        // restore anonymous repository access
        model.accessRestriction = AccessRestrictionType.NONE;
        model.authorizationControl = AuthorizationControl.NAMED;
        GitBlit.self().updateRepositoryModel(model.name, model, false);
        GitBlit.self().deleteUser(user.username);
    }
    @Test
    public void testAnonymousPush() throws Exception {
tests/com/gitblit/tests/RpcTests.java
@@ -33,6 +33,7 @@
import org.junit.Test;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.GitBlitException.UnauthorizedException;
import com.gitblit.Keys;
import com.gitblit.RpcServlet;
@@ -164,6 +165,7 @@
        model.description = "created by RpcUtils";
        model.owner = "garbage";
        model.accessRestriction = AccessRestrictionType.VIEW;
        model.authorizationControl = AuthorizationControl.AUTHENTICATED;
        // create
        assertTrue("Failed to create repository!",
@@ -172,6 +174,7 @@
        RepositoryModel retrievedRepository = findRepository(model.name);
        assertNotNull("Failed to find " + model.name, retrievedRepository);
        assertEquals(AccessRestrictionType.VIEW, retrievedRepository.accessRestriction);
        assertEquals(AuthorizationControl.AUTHENTICATED, retrievedRepository.authorizationControl);
        // rename and change access restriciton
        String originalName = model.name;