From ba6150d1712d5f5986e72333831940a46316aab3 Mon Sep 17 00:00:00 2001 From: James Moger Date: Fri, 2 Nov 2012 16:52:41 -0400 Subject: [PATCH] Permission filtering in web ui Present the mutable permissions by default. Allow the administrator or owner to toggle the displayed permissions to see how the user and team permissions are applied to a repository. --- .gitignore | 1 + src/com/gitblit/GitBlit.java | 61 ++++++++++--- src/com/gitblit/client/GitblitClient.java | 56 +++++++++++- .../client/RegistrantPermissionsPanel.java | 25 +++++- src/com/gitblit/client/UsersPanel.java | 2 +- src/com/gitblit/client/Utils.java | 25 +++++- .../models/RegistrantAccessPermission.java | 51 ++++++++++- src/com/gitblit/models/UserModel.java | 25 +++++- .../gitblit/wicket/GitBlitWebApp.properties | 5 +- .../gitblit/wicket/pages/EditUserPage.java | 22 +---- .../panels/RegistrantPermissionsPanel.html | 20 ++++- .../panels/RegistrantPermissionsPanel.java | 90 +++++++++++++++++-- 12 files changed, 328 insertions(+), 55 deletions(-) diff --git a/.gitignore b/.gitignore index 173bd34f..ffaba310 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ /.gradle /projects.conf /pom.xml +/deploy diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java index 0d883ad2..0d37b44f 100644 --- a/src/com/gitblit/GitBlit.java +++ b/src/com/gitblit/GitBlit.java @@ -79,6 +79,8 @@ import com.gitblit.Constants.AuthorizationControl; 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; import com.gitblit.models.FederationSet; @@ -658,18 +660,48 @@ public class GitBlit implements ServletContextListener { * @return a user object or null */ public UserModel getUserModel(String username) { - UserModel user = userService.getUserModel(username); - if (user != null) { - // TODO reconsider ownership as a user property - // manually specify personal repository ownerships - String folder = "~" + username; - for (String repository : getRepositoryList()) { - if (repository.toLowerCase().startsWith(folder)) { - user.setRepositoryPermission(repository, AccessPermission.REWIND); + UserModel user = userService.getUserModel(username); + return user; + } + + /** + * Returns the effective list of permissions for this user, taking into account + * team memberships, ownerships. + * + * @param user + * @return the effective list of permissions for the user + */ + public List getUserAccessPermissions(UserModel user) { + Set set = new LinkedHashSet(); + set.addAll(user.getRepositoryPermissions()); + // Flag missing repositories + for (RegistrantAccessPermission permission : set) { + if (permission.mutable && PermissionType.EXPLICIT.equals(permission.permissionType)) { + RepositoryModel rm = GitBlit.self().getRepositoryModel(permission.registrant); + if (rm == null) { + permission.permissionType = PermissionType.MISSING; + permission.mutable = false; + continue; } } } - return user; + + // TODO reconsider ownership as a user property + // manually specify personal repository ownerships + for (RepositoryModel rm : repositoryListCache.values()) { + if (rm.isUsersPersonalRepository(user.username) || rm.isOwner(user.username)) { + RegistrantAccessPermission rp = new RegistrantAccessPermission(rm.name, AccessPermission.REWIND, + PermissionType.OWNER, RegistrantType.REPOSITORY, null, false); + // user may be owner of a repository to which they've inherited + // a team permission, replace any existing perm with owner perm + set.remove(rp); + set.add(rp); + } + } + + List list = new ArrayList(set); + Collections.sort(list); + return list; } /** @@ -681,7 +713,16 @@ public class GitBlit implements ServletContextListener { * @return a list of RegistrantAccessPermissions */ public List getUserAccessPermissions(RepositoryModel repository) { - List list = new ArrayList(); + List list = new ArrayList(); + if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) { + // no permissions needed, REWIND for everyone! + return list; + } + if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl)) { + // no permissions needed, REWIND for authenticated! + return list; + } + // NAMED users and teams for (UserModel user : userService.getAllUsers()) { RegistrantAccessPermission ap = user.getRepositoryPermission(repository); if (ap.permission.exceeds(AccessPermission.NONE)) { diff --git a/src/com/gitblit/client/GitblitClient.java b/src/com/gitblit/client/GitblitClient.java index 56078fc3..1101cd60 100644 --- a/src/com/gitblit/client/GitblitClient.java +++ b/src/com/gitblit/client/GitblitClient.java @@ -31,6 +31,8 @@ import com.gitblit.Constants; import com.gitblit.Constants.AccessPermission; import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.Constants.AuthorizationControl; +import com.gitblit.Constants.PermissionType; +import com.gitblit.Constants.RegistrantType; import com.gitblit.GitBlitException.ForbiddenException; import com.gitblit.GitBlitException.NotAllowedException; import com.gitblit.GitBlitException.UnauthorizedException; @@ -505,15 +507,63 @@ public class GitblitClient implements Serializable { return usernames; } + /** + * Returns the effective list of permissions for this user, taking into account + * team memberships, ownerships. + * + * @param user + * @return the effective list of permissions for the user + */ + public List getUserAccessPermissions(UserModel user) { + Set set = new LinkedHashSet(); + set.addAll(user.getRepositoryPermissions()); + // Flag missing repositories + for (RegistrantAccessPermission permission : set) { + if (permission.mutable && PermissionType.EXPLICIT.equals(permission.permissionType)) { + RepositoryModel rm = getRepository(permission.registrant); + if (rm == null) { + permission.permissionType = PermissionType.MISSING; + permission.mutable = false; + continue; + } + } + } + + // TODO reconsider ownership as a user property + // manually specify personal repository ownerships + for (RepositoryModel rm : allRepositories) { + if (rm.isUsersPersonalRepository(user.username) || rm.isOwner(user.username)) { + RegistrantAccessPermission rp = new RegistrantAccessPermission(rm.name, AccessPermission.REWIND, + PermissionType.OWNER, RegistrantType.REPOSITORY, null, false); + // user may be owner of a repository to which they've inherited + // a team permission, replace any existing perm with owner perm + set.remove(rp); + set.add(rp); + } + } + + List list = new ArrayList(set); + Collections.sort(list); + return list; + } + public List getUserAccessPermissions(RepositoryModel repository) { - List list = new ArrayList(); - for (UserModel user : getUsers()) { + List list = new ArrayList(); + if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) { + // no permissions needed, REWIND for everyone! + return list; + } + if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl)) { + // no permissions needed, REWIND for authenticated! + return list; + } + // NAMED users and teams + for (UserModel user : allUsers) { RegistrantAccessPermission ap = user.getRepositoryPermission(repository); if (ap.permission.exceeds(AccessPermission.NONE)) { list.add(ap); } } - Collections.sort(list); return list; } diff --git a/src/com/gitblit/client/RegistrantPermissionsPanel.java b/src/com/gitblit/client/RegistrantPermissionsPanel.java index ef04a876..98dbfb72 100644 --- a/src/com/gitblit/client/RegistrantPermissionsPanel.java +++ b/src/com/gitblit/client/RegistrantPermissionsPanel.java @@ -16,11 +16,14 @@ package com.gitblit.client; import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.swing.DefaultCellEditor; @@ -36,6 +39,7 @@ import javax.swing.table.DefaultTableCellRenderer; import com.gitblit.Constants.AccessPermission; import com.gitblit.Constants.PermissionType; import com.gitblit.Constants.RegistrantType; +import com.gitblit.client.Utils.RowRenderer; import com.gitblit.models.RegistrantAccessPermission; import com.gitblit.utils.StringUtils; @@ -60,7 +64,23 @@ public class RegistrantPermissionsPanel extends JPanel { public RegistrantPermissionsPanel(final RegistrantType registrantType) { super(new BorderLayout(5, 5)); tableModel = new RegistrantPermissionsTableModel(); - permissionsTable = Utils.newTable(tableModel, Utils.DATE_FORMAT); + permissionsTable = Utils.newTable(tableModel, Utils.DATE_FORMAT, new RowRenderer() { + Color clear = new Color(0, 0, 0, 0); + Color iceGray = new Color(0xf0, 0xf0, 0xf0); + + @Override + public void prepareRow(Component c, boolean isSelected, int row, int column) { + if (isSelected) { + c.setBackground(permissionsTable.getSelectionBackground()); + } else { + if (tableModel.permissions.get(row).mutable) { + c.setBackground(clear); + } else { + c.setBackground(iceGray); + } + } + } + }); permissionsTable.setModel(tableModel); permissionsTable.setPreferredScrollableViewportSize(new Dimension(400, 150)); JScrollPane jsp = new JScrollPane(permissionsTable); @@ -91,11 +111,14 @@ public class RegistrantPermissionsPanel extends JPanel { rp.permission = (AccessPermission) permissionSelector.getSelectedItem(); if (StringUtils.findInvalidCharacter(rp.registrant) != null) { rp.permissionType = PermissionType.REGEX; + rp.source = rp.registrant; } else { rp.permissionType = PermissionType.EXPLICIT; } tableModel.permissions.add(rp); + // resort permissions after insert to convey idea of eval order + Collections.sort(tableModel.permissions); registrantModel.removeElement(rp.registrant); registrantSelector.setSelectedIndex(-1); diff --git a/src/com/gitblit/client/UsersPanel.java b/src/com/gitblit/client/UsersPanel.java index 1810f8cb..e14c0010 100644 --- a/src/com/gitblit/client/UsersPanel.java +++ b/src/com/gitblit/client/UsersPanel.java @@ -308,7 +308,7 @@ public abstract class UsersPanel extends JPanel { gitblit.getSettings()); dialog.setLocationRelativeTo(UsersPanel.this); dialog.setUsers(gitblit.getUsers()); - dialog.setRepositories(gitblit.getRepositories(), user.getRepositoryPermissions()); + dialog.setRepositories(gitblit.getRepositories(), gitblit.getUserAccessPermissions(user)); dialog.setTeams(gitblit.getTeams(), user.teams == null ? null : new ArrayList( user.teams)); dialog.setVisible(true); diff --git a/src/com/gitblit/client/Utils.java b/src/com/gitblit/client/Utils.java index b24c6d8e..1e6ab2bf 100644 --- a/src/com/gitblit/client/Utils.java +++ b/src/com/gitblit/client/Utils.java @@ -49,7 +49,25 @@ public class Utils { public final static String DATE_FORMAT = "yyyy-MM-dd"; public static JTable newTable(TableModel model, String datePattern) { - JTable table = new JTable(model); + return newTable(model, datePattern, null); + } + + public static JTable newTable(TableModel model, String datePattern, final RowRenderer rowRenderer) { + JTable table; + if (rowRenderer == null) { + table = new JTable(model); + } else { + table = new JTable(model) { + + @Override + public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { + Component c = super.prepareRenderer(renderer, row, column); + boolean isSelected = isCellSelected(row, column); + rowRenderer.prepareRow(c, isSelected, row, column); + return c; + } + }; + } table.setRowHeight(table.getFont().getSize() + 8); table.setCellSelectionEnabled(false); table.setRowSelectionAllowed(true); @@ -148,5 +166,8 @@ public class Utils { showException(null, x); } } - + + public static abstract class RowRenderer { + public abstract void prepareRow(Component c, boolean isSelected, int row, int column); + } } diff --git a/src/com/gitblit/models/RegistrantAccessPermission.java b/src/com/gitblit/models/RegistrantAccessPermission.java index 4bdc2da4..8f4049a8 100644 --- a/src/com/gitblit/models/RegistrantAccessPermission.java +++ b/src/com/gitblit/models/RegistrantAccessPermission.java @@ -63,18 +63,67 @@ public class RegistrantAccessPermission implements Serializable, Comparable list.add(new RegistrantAccessPermission(registrant, ap, pType, RegistrantType.REPOSITORY, source, mutable)); } Collections.sort(list); - return list; + + // include immutable team permissions, being careful to preserve order + Set set = new LinkedHashSet(list); + for (TeamModel team : teams) { + for (RegistrantAccessPermission teamPermission : team.getRepositoryPermissions()) { + // we can not change an inherited team permission, though we can override + teamPermission.registrantType = RegistrantType.REPOSITORY; + teamPermission.permissionType = PermissionType.TEAM; + teamPermission.source = team.name; + teamPermission.mutable = false; + set.add(teamPermission); + } + } + return new ArrayList(set); } /** @@ -253,6 +267,13 @@ public class UserModel implements Principal, Serializable, Comparable ap.permission = AccessPermission.NONE; ap.mutable = false; + if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) { + // anonymous rewind + ap.permissionType = PermissionType.ADMINISTRATOR; + ap.permission = AccessPermission.REWIND; + return ap; + } + // administrator if (canAdmin()) { ap.permissionType = PermissionType.ADMINISTRATOR; @@ -277,7 +298,7 @@ public class UserModel implements Principal, Serializable, Comparable } if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl) && isAuthenticated) { - // AUTHENTICATED is a shortcut for authorizing all logged-in users RW access + // AUTHENTICATED is a shortcut for authorizing all logged-in users RW+ access ap.permission = AccessPermission.REWIND; return ap; } diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties index 4303b134..22ae92f7 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp.properties +++ b/src/com/gitblit/wicket/GitBlitWebApp.properties @@ -370,4 +370,7 @@ gb.administratorPermission = Gitblit administrator gb.team = team gb.teamPermission = permission set by \"{0}\" team membership gb.missing = missing! -gb.missingPermission = the repository for this permission is missing! \ No newline at end of file +gb.missingPermission = the repository for this permission is missing! +gb.mutable = mutable +gb.specified = specified +gb.effective = effective diff --git a/src/com/gitblit/wicket/pages/EditUserPage.java b/src/com/gitblit/wicket/pages/EditUserPage.java index ea92293e..80f09dba 100644 --- a/src/com/gitblit/wicket/pages/EditUserPage.java +++ b/src/com/gitblit/wicket/pages/EditUserPage.java @@ -34,13 +34,11 @@ import org.apache.wicket.model.Model; import org.apache.wicket.model.util.CollectionModel; import org.apache.wicket.model.util.ListModel; -import com.gitblit.Constants.PermissionType; import com.gitblit.Constants.RegistrantType; import com.gitblit.GitBlit; import com.gitblit.GitBlitException; import com.gitblit.Keys; import com.gitblit.models.RegistrantAccessPermission; -import com.gitblit.models.RepositoryModel; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.StringUtils; @@ -104,25 +102,7 @@ public class EditUserPage extends RootSubPage { Collections.sort(userTeams); final String oldName = userModel.username; - final List permissions = userModel.getRepositoryPermissions(); - for (RegistrantAccessPermission permission : permissions) { - if (permission.mutable && 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. - RepositoryModel rm = GitBlit.self().getRepositoryModel(permission.registrant); - if (rm == null) { - permission.permissionType = PermissionType.MISSING; - permission.mutable = false; - continue; - } - boolean isOwner = rm.isOwner(oldName); - if (isOwner) { - permission.permissionType = PermissionType.OWNER; - permission.mutable = false; - } - } - } + final List permissions = GitBlit.self().getUserAccessPermissions(userModel); final Palette teams = new Palette("teams", new ListModel( new ArrayList(userTeams)), new CollectionModel(GitBlit.self() diff --git a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html index ec8d43dd..eb82245c 100644 --- a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html +++ b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html @@ -7,20 +7,32 @@ -
+
+
+ + + +
+
+ +
[permission type]
-
+
- + + + + + - + diff --git a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java index 9431df8f..689ee571 100644 --- a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java +++ b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java @@ -18,10 +18,12 @@ package com.gitblit.wicket.panels; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; +import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; import org.apache.wicket.ajax.markup.html.form.AjaxButton; @@ -57,12 +59,42 @@ import com.gitblit.wicket.WicketUtils; public class RegistrantPermissionsPanel extends BasePanel { private static final long serialVersionUID = 1L; - + + public enum Show { + specified, mutable, effective; + + public boolean show(RegistrantAccessPermission ap) { + switch (this) { + case specified: + return ap.mutable || ap.isOwner(); + case mutable: + return ap.mutable; + case effective: + return true; + default: + return true; + } + } + } + + private Show activeState = Show.mutable; + public RegistrantPermissionsPanel(String wicketId, RegistrantType registrantType, List allRegistrants, final List permissions, final Map translations) { super(wicketId); setOutputMarkupId(true); - - // update existing permissions repeater + + /* + * Permission view toggle buttons + */ + Form permissionToggleForm = new Form("permissionToggleForm"); + permissionToggleForm.add(new ShowStateButton("showSpecified", Show.specified)); + permissionToggleForm.add(new ShowStateButton("showMutable", Show.mutable)); + permissionToggleForm.add(new ShowStateButton("showEffective", Show.effective)); + add(permissionToggleForm); + + /* + * Permission repeating display + */ RefreshingView dataView = new RefreshingView("permissionRow") { private static final long serialVersionUID = 1L; @@ -91,16 +123,19 @@ public class RegistrantPermissionsPanel extends BasePanel { String repoName = StringUtils.stripDotGit(entry.registrant); if (!entry.isMissing() && 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); + Fragment repositoryFragment = new Fragment("registrant", "repositoryRegistrant", RegistrantPermissionsPanel.this); + Component swatch = new Label("repositorySwatch", " ").setEscapeModelStrings(false); + WicketUtils.setCssBackground(swatch, entry.toString()); + repositoryFragment.add(swatch); + Label registrant = new Label("repositoryName", repoName); + repositoryFragment.add(registrant); + item.add(repositoryFragment); } else { // regex or missing Label label = new Label("registrant", entry.registrant); WicketUtils.setCssStyle(label, "font-weight: bold;"); item.add(label); - } + } } else if (RegistrantType.USER.equals(entry.registrantType)) { // user PersonIdent ident = new PersonIdent(entry.registrant, null); @@ -160,6 +195,8 @@ public class RegistrantPermissionsPanel extends BasePanel { break; } + item.setVisible(activeState.show(entry)); + // use ajax to get immediate update of permission level change // otherwise we can lose it if they change levels and then add // a new repository permission @@ -203,7 +240,9 @@ public class RegistrantPermissionsPanel extends BasePanel { } } - // add new permission form + /* + * Add permission form + */ IModel addPermissionModel = new CompoundPropertyModel(new RegistrantAccessPermission(registrantType)); Form addPermissionForm = new Form("addPermissionForm", addPermissionModel); addPermissionForm.add(new DropDownChoice("registrant", registrants)); @@ -223,9 +262,13 @@ public class RegistrantPermissionsPanel extends BasePanel { RegistrantAccessPermission copy = DeepCopier.copy(rp); if (StringUtils.findInvalidCharacter(copy.registrant) != null) { copy.permissionType = PermissionType.REGEX; + copy.source = copy.registrant; } permissions.add(copy); + // resort permissions after insert to convey idea of eval order + Collections.sort(permissions); + // remove registrant from available choices registrants.remove(rp.registrant); @@ -265,4 +308,33 @@ public class RegistrantPermissionsPanel extends BasePanel { return Integer.toString(index); } } + + private class ShowStateButton extends AjaxButton { + private static final long serialVersionUID = 1L; + + Show buttonState; + + public ShowStateButton(String wicketId, Show state) { + super(wicketId); + this.buttonState = state; + setOutputMarkupId(true); + } + + @Override + protected void onBeforeRender() + { + String cssClass = "btn"; + if (buttonState.equals(RegistrantPermissionsPanel.this.activeState)) { + cssClass = "btn btn-info active"; + } + WicketUtils.setCssClass(this, cssClass); + super.onBeforeRender(); + } + + @Override + protected void onSubmit(AjaxRequestTarget target, Form form) { + RegistrantPermissionsPanel.this.activeState = buttonState; + target.addComponent(RegistrantPermissionsPanel.this); + } + }; } -- 2.39.5