James Moger
2012-10-10 7f70511e9a13f4801e4e941affad6fc7b579c79d
Support Team canAdmin, canCreate, and canFork (issue-36)
22 files modified
210 ■■■■ changed files
src/com/gitblit/AccessRestrictionFilter.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/AuthenticationFilter.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/ConfigUserService.java 24 ●●●●● patch | view | raw | blame | history
src/com/gitblit/FileUserService.java 25 ●●●●● patch | view | raw | blame | history
src/com/gitblit/RpcFilter.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/RpcServlet.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/client/UsersTableModel.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/models/TeamModel.java 3 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/UserModel.java 75 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/AuthorizationStrategy.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/GitBlitWebApp.properties 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/GitBlitWebSession.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditRepositoryPage.java 10 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditTeamPage.html 7 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditTeamPage.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditUserPage.html 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/RepositoryPage.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/UserPage.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/RepositoriesPanel.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/UsersPanel.java 2 ●●● patch | view | raw | blame | history
test-users.conf 1 ●●●● patch | view | raw | blame | history
tests/com/gitblit/tests/PermissionsTest.java 29 ●●●●● patch | view | raw | blame | history
src/com/gitblit/AccessRestrictionFilter.java
@@ -156,7 +156,7 @@
                return;
            } else {
                // check user access for request
                if (user.canAdmin || canAccess(model, user, urlRequestType)) {
                if (user.canAdmin() || canAccess(model, user, urlRequestType)) {
                    // authenticated request permitted.
                    // pass processing to the restricted servlet.
                    newSession(authenticatedRequest, httpResponse);
src/com/gitblit/AuthenticationFilter.java
@@ -189,7 +189,7 @@
        @Override
        public boolean isUserInRole(String role) {
            if (role.equals(Constants.ADMIN_ROLE)) {
                return user.canAdmin;
                return user.canAdmin();
            }
            // Gitblit does not currently use actual roles in the traditional
            // servlet container sense.  That is the reason this is marked
src/com/gitblit/ConfigUserService.java
@@ -862,6 +862,24 @@
        // write teams
        for (TeamModel model : teams.values()) {
            // team roles
            List<String> roles = new ArrayList<String>();
            if (model.canAdmin) {
                roles.add(Constants.ADMIN_ROLE);
            }
            if (model.canFork) {
                roles.add(Constants.FORK_ROLE);
            }
            if (model.canCreate) {
                roles.add(Constants.CREATE_ROLE);
            }
            if (roles.size() == 0) {
                // we do this to ensure that team record is written.
                // Otherwise, StoredConfig might optimizes that record away.
                roles.add(Constants.NO_ROLE);
            }
            config.setStringList(TEAM, model.name, ROLE, roles);
            if (model.permissions == null) {
                // null check on "final" repositories because JSON-sourced TeamModel
                // can have a null repositories object
@@ -982,6 +1000,12 @@
                Set<String> teamnames = config.getSubsections(TEAM);
                for (String teamname : teamnames) {
                    TeamModel team = new TeamModel(teamname);
                    Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
                            TEAM, teamname, ROLE)));
                    team.canAdmin = roles.contains(Constants.ADMIN_ROLE);
                    team.canFork = roles.contains(Constants.FORK_ROLE);
                    team.canCreate = roles.contains(Constants.CREATE_ROLE);
                    team.addRepositoryPermissions(Arrays.asList(config.getStringList(TEAM, teamname,
                            REPOSITORY)));
                    team.addUsers(Arrays.asList(config.getStringList(TEAM, teamname, USER)));
src/com/gitblit/FileUserService.java
@@ -780,6 +780,20 @@
                        } else if (role.charAt(0) == '%') {
                            postReceive.add(role.substring(1));
                        } else {
                            switch (role.charAt(0)) {
                            case '#':
                                // Permissions
                                if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) {
                                    team.canAdmin = true;
                                } else if (role.equalsIgnoreCase(Constants.FORK_ROLE)) {
                                    team.canFork = true;
                                } else if (role.equalsIgnoreCase(Constants.CREATE_ROLE)) {
                                    team.canCreate = true;
                                }
                                break;
                            default:
                                repositories.add(role);
                            }
                            repositories.add(role);
                        }
                    }
@@ -1040,6 +1054,17 @@
            }
        }
        
        // Permissions
        if (model.canAdmin) {
            roles.add(Constants.ADMIN_ROLE);
        }
        if (model.canFork) {
            roles.add(Constants.FORK_ROLE);
        }
        if (model.canCreate) {
            roles.add(Constants.CREATE_ROLE);
        }
        for (String role : roles) {
                sb.append(role);
                sb.append(',');
src/com/gitblit/RpcFilter.java
@@ -105,7 +105,7 @@
                return;
            } else {
                // check user access for request
                if (user.canAdmin || canAccess(user, requestType)) {
                if (user.canAdmin() || canAccess(user, requestType)) {
                    // authenticated request permitted.
                    // pass processing to the restricted servlet.
                    newSession(authenticatedRequest, httpResponse);
@@ -140,7 +140,7 @@
        case LIST_REPOSITORIES:
            return true;
        default:
            return user.canAdmin;
            return user.canAdmin();
        }
    }
}
src/com/gitblit/RpcServlet.java
@@ -73,10 +73,10 @@
        UserModel user = (UserModel) request.getUserPrincipal();
        boolean allowManagement = user != null && user.canAdmin
        boolean allowManagement = user != null && user.canAdmin()
                && GitBlit.getBoolean(Keys.web.enableRpcManagement, false);
        boolean allowAdmin = user != null && user.canAdmin
        boolean allowAdmin = user != null && user.canAdmin()
                && GitBlit.getBoolean(Keys.web.enableRpcAdministration, false);
        Object result = null;
src/com/gitblit/client/UsersTableModel.java
@@ -102,7 +102,7 @@
        case Display_Name:
            return model.displayName;
        case AccessLevel:
            if (model.canAdmin) {
            if (model.canAdmin()) {
                return "administrator";
            }
            return "";
src/com/gitblit/models/TeamModel.java
@@ -41,6 +41,9 @@
    // field names are reflectively mapped in EditTeam page
    public String name;
    public boolean canAdmin;
    public boolean canFork;
    public boolean canCreate;
    public final Set<String> users = new HashSet<String>();
    // retained for backwards-compatibility with RPC clients
    @Deprecated
src/com/gitblit/models/UserModel.java
@@ -26,6 +26,7 @@
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.Unused;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
/**
@@ -80,7 +81,7 @@
     */
    @Deprecated
    public boolean canAccessRepository(String repositoryName) {
        return canAdmin || repositories.contains(repositoryName.toLowerCase())
        return canAdmin() || repositories.contains(repositoryName.toLowerCase())
                || hasTeamAccess(repositoryName);
    }
@@ -90,7 +91,7 @@
        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())
        return canAdmin() || isOwner || repositories.contains(repository.name.toLowerCase())
                || hasTeamAccess(repository.name) || allowAuthenticated;
    }
@@ -177,7 +178,7 @@
    }
    public AccessPermission getRepositoryPermission(RepositoryModel repository) {
        if (canAdmin || repository.isOwner(username) || repository.isUsersPersonalRepository(username)) {
        if (canAdmin() || repository.isOwner(username) || repository.isUsersPersonalRepository(username)) {
            return AccessPermission.REWIND;
        }
        if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl) && isAuthenticated) {
@@ -265,24 +266,84 @@
            // can not fork your own repository
            return false;
        }
        if (canAdmin || repository.isOwner(username)) {
        if (canAdmin() || repository.isOwner(username)) {
            return true;
        }
        if (!repository.allowForks) {
            return false;
        }
        if (!isAuthenticated || !canFork) {
        if (!isAuthenticated || !canFork()) {
            return false;
        }
        return canClone(repository);
    }
    
    public boolean canDelete(RepositoryModel model) {
        return canAdmin || model.isUsersPersonalRepository(username);
        return canAdmin() || model.isUsersPersonalRepository(username);
    }
    
    public boolean canEdit(RepositoryModel model) {
        return canAdmin || model.isUsersPersonalRepository(username) || model.isOwner(username);
        return canAdmin() || model.isUsersPersonalRepository(username) || model.isOwner(username);
    }
    /**
     * This returns true if the user has fork privileges or the user has fork
     * privileges because of a team membership.
     *
     * @return true if the user can fork
     */
    public boolean canFork() {
        if (canFork) {
            return true;
        }
        if (!ArrayUtils.isEmpty(teams)) {
            for (TeamModel team : teams) {
                if (team.canFork) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * This returns true if the user has admin privileges or the user has admin
     * privileges because of a team membership.
     *
     * @return true if the user can admin
     */
    public boolean canAdmin() {
        if (canAdmin) {
            return true;
        }
        if (!ArrayUtils.isEmpty(teams)) {
            for (TeamModel team : teams) {
                if (team.canAdmin) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * This returns true if the user has create privileges or the user has create
     * privileges because of a team membership.
     *
     * @return true if the user can admin
     */
    public boolean canCreate() {
        if (canCreate) {
            return true;
        }
        if (!ArrayUtils.isEmpty(teams)) {
            for (TeamModel team : teams) {
                if (team.canCreate) {
                    return true;
                }
            }
        }
        return false;
    }
    public boolean isTeamMember(String teamname) {
src/com/gitblit/wicket/AuthorizationStrategy.java
@@ -60,7 +60,7 @@
                    if (authenticateAdmin) {
                        // authenticate admin
                        if (user != null) {
                            return user.canAdmin;
                            return user.canAdmin();
                        }
                        return false;
                    } else {
src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -329,7 +329,7 @@
gb.allowForksDescription = allow authorized users to fork this repository
gb.forkedFrom = forked from
gb.canFork = can fork
gb.canForkDescription = user is permitted to fork authorized repositories
gb.canForkDescription = can fork authorized repositories to a personal repository
gb.myFork = view my fork
gb.forksProhibited = forks prohibited
gb.forksProhibitedWarning = this repository forbids forks
src/com/gitblit/wicket/GitBlitWebSession.java
@@ -103,7 +103,7 @@
        if (user == null) {
            return false;
        }
        return user.canAdmin;
        return user.canAdmin();
    }
    
    public String getUsername() {
src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -83,7 +83,7 @@
        
        GitBlitWebSession session = GitBlitWebSession.get();
        UserModel user = session.getUser();
        if (user != null && user.canCreate && !user.canAdmin) {
        if (user != null && user.canCreate() && !user.canAdmin()) {
            // personal create permissions, inject personal repository path
            model.name = user.getPersonalPath() + "/";
            model.projectPath = user.getPersonalPath();
@@ -120,7 +120,7 @@
        final UserModel user = session.getUser() == null ? UserModel.ANONYMOUS : session.getUser();
        if (isCreate) {
            if (user.canAdmin) {
            if (user.canAdmin()) {
                super.setupPage(getString("gb.newRepository"), "");
            } else {
                super.setupPage(getString("gb.newRepository"), user.getDisplayName());
@@ -253,7 +253,7 @@
                        return;
                    }
                    
                    if (user.canCreate && !user.canAdmin) {
                    if (user.canCreate() && !user.canAdmin()) {
                        // ensure repository name begins with the user's path
                        if (!repositoryModel.name.startsWith(user.getPersonalPath())) {
                            error(MessageFormat.format(getString("gb.illegalPersonalRepositoryLocation"),
@@ -474,13 +474,13 @@
                }
                if (isCreate) {
                    // Create Repository
                    if (!user.canCreate && !user.canAdmin) {
                    if (!user.canCreate() && !user.canAdmin()) {
                        // Only administrators or permitted users may create
                        error(getString("gb.errorOnlyAdminMayCreateRepository"), true);
                    }
                } else {
                    // Edit Repository
                    if (user.canAdmin) {
                    if (user.canAdmin()) {
                        // Admins can edit everything
                        isAdmin = true;
                        return;
src/com/gitblit/wicket/pages/EditTeamPage.html
@@ -12,7 +12,10 @@
            <tbody class="settings">
                <tr><td colspan="2"><h3><wicket:message key="gb.general"></wicket:message> &nbsp;<small><wicket:message key="gb.generalDescription"></wicket:message></small></h3></td></tr>
                <tr><th><wicket:message key="gb.teamName"></wicket:message></th><td class="edit"><input type="text" wicket:id="name" id="name" size="30" tabindex="1" /></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="2" /></td></tr>
                <tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canAdmin" tabindex="2" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canAdminDescription"></wicket:message></span></label></td></tr>
                <tr><th><wicket:message key="gb.canCreate"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canCreate" tabindex="3" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canCreateDescription"></wicket:message></span></label></td></tr>
                <tr><th><wicket:message key="gb.canFork"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canFork" tabindex="4" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canForkDescription"></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="5" /></td></tr>
                <tr><td colspan="2" style="padding-top:15px;"><h3><wicket:message key="gb.accessPermissions"></wicket:message> &nbsp;<small><wicket:message key="gb.accessPermissionsForTeamDescription"></wicket:message></small></h3></td></tr>    
                <tr><th style="vertical-align: top;"><wicket:message key="gb.teamMembers"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr>
                <tr><td colspan="2"><hr></hr></td></tr>
@@ -20,7 +23,7 @@
                <tr><td colspan="2" style="padding-top:10px;"><h3><wicket:message key="gb.hookScripts"></wicket:message> &nbsp;<small><wicket:message key="gb.hookScriptsDescription"></wicket:message></small></h3></td></tr>    
                <tr><th style="vertical-align: top;"><wicket:message key="gb.preReceiveScripts"></wicket:message><p></p><span wicket:id="inheritedPreReceive"></span></th><td style="padding:2px;"><span wicket:id="preReceiveScripts"></span></td></tr>
                <tr><th style="vertical-align: top;"><wicket:message key="gb.postReceiveScripts"></wicket:message><p></p><span wicket:id="inheritedPostReceive"></span></th><td style="padding:2px;"><span wicket:id="postReceiveScripts"></span></td></tr>
                <tr><td colspan='2'><div class="form-actions"><input class="btn btn-primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="3" /> &nbsp; <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="4" /></div></td></tr>
                <tr><td colspan='2'><div class="form-actions"><input class="btn btn-primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="6" /> &nbsp; <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="7" /></div></td></tr>
            </tbody>
        </table>
    </form>    
src/com/gitblit/wicket/pages/EditTeamPage.java
@@ -27,6 +27,7 @@
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.extensions.markup.html.form.palette.Palette;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.CompoundPropertyModel;
@@ -222,6 +223,9 @@
        
        // field names reflective match TeamModel fields
        form.add(new TextField<String>("name"));
        form.add(new CheckBox("canAdmin"));
        form.add(new CheckBox("canFork"));
        form.add(new CheckBox("canCreate"));
        form.add(users.setEnabled(editMemberships));
        mailingLists = new Model<String>(teamModel.mailingLists == null ? ""
                : StringUtils.flattenStrings(teamModel.mailingLists, " "));
src/com/gitblit/wicket/pages/EditUserPage.html
@@ -17,8 +17,8 @@
                <tr><th><wicket:message key="gb.displayName"></wicket:message></th><td class="edit"><input type="text" wicket:id="displayName" size="30" tabindex="4" /></td></tr>
                <tr><th><wicket:message key="gb.emailAddress"></wicket:message></th><td class="edit"><input type="text" wicket:id="emailAddress" size="30" tabindex="5" /></td></tr>
                <tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canAdminDescription"></wicket:message></span></label></td></tr>                
                <tr><th><wicket:message key="gb.canFork"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canFork" tabindex="7" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canForkDescription"></wicket:message></span></label></td></tr>
                <tr><th><wicket:message key="gb.canCreate"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canCreate" tabindex="8" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canCreateDescription"></wicket:message></span></label></td></tr>
                <tr><th><wicket:message key="gb.canCreate"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canCreate" tabindex="7" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canCreateDescription"></wicket:message></span></label></td></tr>
                <tr><th><wicket:message key="gb.canFork"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canFork" tabindex="8" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canForkDescription"></wicket:message></span></label></td></tr>
                <tr><th><wicket:message key="gb.excludeFromFederation"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="excludeFromFederation" tabindex="9" /> &nbsp;<span class="help-inline"><wicket:message key="gb.excludeFromFederationDescription"></wicket:message></span></label></td></tr>                
                <tr><td colspan="2" style="padding-top:15px;"><h3><wicket:message key="gb.accessPermissions"></wicket:message> &nbsp;<small><wicket:message key="gb.accessPermissionsForUserDescription"></wicket:message></small></h3></td></tr>    
                <tr><th style="vertical-align: top;"><wicket:message key="gb.teamMemberships"></wicket:message></th><td style="padding:2px;"><span wicket:id="teams"></span></td></tr>
src/com/gitblit/wicket/pages/RepositoryPage.java
@@ -248,7 +248,7 @@
                // user not allowed to fork or fork already exists or repo forbids forking
                add(new ExternalLink("forkLink", "").setVisible(false));
                
                if (user.canFork && !model.allowForks) {
                if (user.canFork() && !model.allowForks) {
                    // show forks prohibited indicator
                    Fragment wc = new Fragment("forksProhibitedIndicator", "forksProhibitedFragment", this);
                    Label lbl = new Label("forksProhibited", getString("gb.forksProhibited"));
src/com/gitblit/wicket/pages/UserPage.java
@@ -101,7 +101,7 @@
        add(new GravatarImage("gravatar", person, 210));
        
        UserModel sessionUser = GitBlitWebSession.get().getUser();
        if (sessionUser != null && user.canCreate && sessionUser.equals(user)) {
        if (sessionUser != null && user.canCreate() && sessionUser.equals(user)) {
            // user can create personal repositories
            add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
        } else {
src/com/gitblit/wicket/panels/RepositoriesPanel.java
@@ -92,7 +92,7 @@
            }.setVisible(GitBlit.getBoolean(Keys.git.cacheRepositoryList, true)));
            managementLinks.add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
            add(managementLinks);
        } else if (showManagement && user != null && user.canCreate) {
        } else if (showManagement && user != null && user.canCreate()) {
            // user can create personal repositories
            managementLinks = new Fragment("managementPanel", "personalLinks", this);
            managementLinks.add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
src/com/gitblit/wicket/panels/UsersPanel.java
@@ -81,7 +81,7 @@
                    item.add(editLink);
                }
                item.add(new Label("accesslevel", entry.canAdmin ? "administrator" : ""));
                item.add(new Label("accesslevel", entry.canAdmin() ? "administrator" : ""));
                item.add(new Label("teams", entry.teams.size() > 0 ? ("" + entry.teams.size()) : ""));
                item.add(new Label("repositories",
                        entry.repositories.size() > 0 ? ("" + entry.repositories.size()) : ""));
test-users.conf
@@ -4,4 +4,5 @@
    role = "#admin"
    role = "#notfederated"
[team "admins"]
    role = "#none"
    user = admin
tests/com/gitblit/tests/PermissionsTest.java
@@ -2414,6 +2414,33 @@
        
        assertFalse("user CAN delete!", user.canDelete(repository));
        assertFalse("user CAN edit!", user.canEdit(repository));
    }
    @Test
    public void testAdminTeamInheritance() throws Exception {
        UserModel user = new UserModel("test");
        TeamModel team = new TeamModel("team");
        team.canAdmin = true;
        user.teams.add(team);
        assertTrue("User did not inherit admin privileges", user.canAdmin());
    }
    @Test
    public void testForkTeamInheritance() throws Exception {
        UserModel user = new UserModel("test");
        TeamModel team = new TeamModel("team");
        team.canFork = true;
        user.teams.add(team);
        assertTrue("User did not inherit fork privileges", user.canFork());
    }
    @Test
    public void testCreateTeamInheritance() throws Exception {
        UserModel user = new UserModel("test");
        TeamModel team = new TeamModel("team");
        team.canCreate= true;
        user.teams.add(team);
        assertTrue("User did not inherit create privileges", user.canCreate());
    }
}