Browse Source

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.
tags/v1.2.0
James Moger 11 years ago
parent
commit
ba6150d171

+ 1
- 0
.gitignore View File

@@ -28,3 +28,4 @@
/.gradle
/projects.conf
/pom.xml
/deploy

+ 51
- 10
src/com/gitblit/GitBlit.java View File

@@ -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<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) {
Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>();
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<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(set);
Collections.sort(list);
return list;
}
/**
@@ -681,7 +713,16 @@ public class GitBlit implements ServletContextListener {
* @return a list of RegistrantAccessPermissions
*/
public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
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)) {

+ 53
- 3
src/com/gitblit/client/GitblitClient.java View File

@@ -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<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) {
Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>();
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<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(set);
Collections.sort(list);
return list;
}
public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
for (UserModel user : getUsers()) {
List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
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;
}

+ 24
- 1
src/com/gitblit/client/RegistrantPermissionsPanel.java View File

@@ -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);

+ 1
- 1
src/com/gitblit/client/UsersPanel.java View File

@@ -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<TeamModel>(
user.teams));
dialog.setVisible(true);

+ 23
- 2
src/com/gitblit/client/Utils.java View File

@@ -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);
}
}

+ 50
- 1
src/com/gitblit/models/RegistrantAccessPermission.java View File

@@ -63,18 +63,67 @@ public class RegistrantAccessPermission implements Serializable, Comparable<Regi
public boolean isOwner() {
return PermissionType.OWNER.equals(permissionType);
}
public boolean isExplicit() {
return PermissionType.EXPLICIT.equals(permissionType);
}

public boolean isRegex() {
return PermissionType.REGEX.equals(permissionType);
}

public boolean isTeam() {
return PermissionType.TEAM.equals(permissionType);
}

public boolean isMissing() {
return PermissionType.MISSING.equals(permissionType);
}
public int getScore() {
switch (registrantType) {
case REPOSITORY:
if (isAdmin()) {
return 0;
}
if (isOwner()) {
return 1;
}
if (isExplicit()) {
return 2;
}
if (isRegex()) {
return 3;
}
if (isTeam()) {
return 4;
}
default:
return 0;
}
}
@Override
public int compareTo(RegistrantAccessPermission p) {
switch (registrantType) {
case REPOSITORY:
// repository permissions are sorted in score order
// to convey the order in which permissions are tested
int score1 = getScore();
int score2 = p.getScore();
if (score1 <= 2 && score2 <= 2) {
// group admin, owner, and explicit together
return StringUtils.compareRepositoryNames(registrant, p.registrant);
}
if (score1 < score2) {
return -1;
} else if (score2 < score1) {
return 1;
}
return StringUtils.compareRepositoryNames(registrant, p.registrant);
default:
return registrant.toLowerCase().compareTo(p.registrant.toLowerCase());
// user and team permissions are string sorted
return registrant.toLowerCase().compareTo(p.registrant.toLowerCase());
}
}

+ 23
- 2
src/com/gitblit/models/UserModel.java View File

@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -160,7 +161,20 @@ public class UserModel implements Principal, Serializable, Comparable<UserModel>
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<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>(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<RegistrantAccessPermission>(set);
}
/**
@@ -253,6 +267,13 @@ public class UserModel implements Principal, Serializable, Comparable<UserModel>
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<UserModel>
}
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;
}

+ 4
- 1
src/com/gitblit/wicket/GitBlitWebApp.properties View File

@@ -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!
gb.missingPermission = the repository for this permission is missing!
gb.mutable = mutable
gb.specified = specified
gb.effective = effective

+ 1
- 21
src/com/gitblit/wicket/pages/EditUserPage.java View File

@@ -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<RegistrantAccessPermission> 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<RegistrantAccessPermission> permissions = GitBlit.self().getUserAccessPermissions(userModel);
final Palette<String> teams = new Palette<String>("teams", new ListModel<String>(
new ArrayList<String>(userTeams)), new CollectionModel<String>(GitBlit.self()

+ 16
- 4
src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html View File

@@ -7,20 +7,32 @@
<body>
<wicket:panel>
<div wicket:id="permissionRow">
<form class="form-inline" wicket:id="permissionToggleForm">
<div style="padding-bottom:10px" class="btn-group pull-right" data-toggle="buttons-radio">
<a class="btn btn-info" wicket:id="showMutable"><wicket:message key="gb.mutable"></wicket:message></a>
<a class="btn btn-info" wicket:id="showSpecified"><wicket:message key="gb.specified"></wicket:message></a>
<a class="btn btn-info" wicket:id="showEffective"><wicket:message key="gb.effective"></wicket:message></a>
</div>
</form>
<div style="clear:both;" 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="span3"><span class="label" wicket:id="pType">[permission type]</span></div> <select class="input-medium" wicket:id="permission"></select>
</div>
</div>
<div style="padding-top:15px;" class="row-fluid">
<div style="clear:both; padding-top:15px;" class="row-fluid">
<form style="padding: 20px 40px;" class="well form-inline" wicket:id="addPermissionForm">
<select class="input-xlarge" wicket:id="registrant"></select> <select class="input-large" wicket:id="permission"></select> <input class="btn btn-success" type="submit" value="Add" wicket:message="value:gb.add" wicket:id="addPermissionButton"/>
</form>
</div>
<wicket:fragment wicket:id="repositoryRegistrant">
<b><span class="repositorySwatch" wicket:id="repositorySwatch"></span></b> <span wicket:id="repositoryName"></span>
</wicket:fragment>
<wicket:fragment wicket:id="userRegistrant">
<span wicket:id="userAvatar"></span> <span style="font-weight: bold;" wicket:id="userName"></span>
<span wicket:id="userAvatar"></span> <span wicket:id="userName"></span>
</wicket:fragment>
<wicket:fragment wicket:id="teamRegistrant">

+ 81
- 9
src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java View File

@@ -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<String> allRegistrants, final List<RegistrantAccessPermission> permissions, final Map<AccessPermission, String> translations) {
super(wicketId);
setOutputMarkupId(true);
// update existing permissions repeater

/*
* Permission view toggle buttons
*/
Form<Void> permissionToggleForm = new Form<Void>("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<RegistrantAccessPermission> dataView = new RefreshingView<RegistrantAccessPermission>("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", "&nbsp;").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<RegistrantAccessPermission> addPermissionModel = new CompoundPropertyModel<RegistrantAccessPermission>(new RegistrantAccessPermission(registrantType));
Form<RegistrantAccessPermission> addPermissionForm = new Form<RegistrantAccessPermission>("addPermissionForm", addPermissionModel);
addPermissionForm.add(new DropDownChoice<String>("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);
}
};
}

Loading…
Cancel
Save