blob: 447e17895a6b3da9171bc38d449a3cf6f76d80dd [file] [log] [blame]
/*
* Copyright 2012 gitblit.com.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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;
import org.apache.wicket.markup.html.basic.Label;
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.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.OddEvenItem;
import org.apache.wicket.markup.repeater.RefreshingView;
import org.apache.wicket.markup.repeater.util.ModelIteratorAdapter;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.eclipse.jgit.lib.PersonIdent;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.UserModel;
import com.gitblit.utils.DeepCopier;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.WicketUtils;
/**
* Allows user to manipulate registrant access permissions.
*
* @author James Moger
*
*/
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);
/*
* 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;
@Override
protected Iterator<IModel<RegistrantAccessPermission>> getItemModels() {
// the iterator returns RepositoryPermission objects, but we need it to
// return models
return new ModelIteratorAdapter<RegistrantAccessPermission>(permissions.iterator()) {
@Override
protected IModel<RegistrantAccessPermission> model(RegistrantAccessPermission permission) {
return new CompoundPropertyModel<RegistrantAccessPermission>(permission);
}
};
}
@Override
protected Item<RegistrantAccessPermission> newItem(String id, int index, IModel<RegistrantAccessPermission> model) {
// this item sets markup class attribute to either 'odd' or
// 'even' for decoration
return new OddEvenItem<RegistrantAccessPermission>(id, index, model);
}
@Override
public void populateItem(final Item<RegistrantAccessPermission> item) {
final RegistrantAccessPermission entry = item.getModelObject();
if (RegistrantType.REPOSITORY.equals(entry.registrantType)) {
String repoName = StringUtils.stripDotGit(entry.registrant);
if (!entry.isMissing() && StringUtils.findInvalidCharacter(repoName) == null) {
// repository, strip .git and show swatch
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, "");
UserModel user = app().users().getUserModel(entry.registrant);
if (user != null) {
ident = new PersonIdent(user.getDisplayName(), user.emailAddress == null ? user.getDisplayName() : user.emailAddress);
}
Fragment userFragment = new Fragment("registrant", "userRegistrant", RegistrantPermissionsPanel.this);
userFragment.add(new AvatarImage("userAvatar", ident, 20));
userFragment.add(new Label("userName", entry.registrant));
item.add(userFragment);
} else {
// team
Fragment teamFragment = new Fragment("registrant", "teamRegistrant", RegistrantPermissionsPanel.this);
teamFragment.add(new Label("teamName", entry.registrant));
item.add(teamFragment);
}
switch (entry.permissionType) {
case ADMINISTRATOR:
Label administrator = new Label("pType", entry.source == null ? getString("gb.administrator") : entry.source);
WicketUtils.setHtmlTooltip(administrator, getString("gb.administratorPermission"));
WicketUtils.setCssClass(administrator, "label label-inverse");
item.add(administrator);
break;
case OWNER:
Label owner = new Label("pType", getString("gb.owner"));
WicketUtils.setHtmlTooltip(owner, getString("gb.ownerPermission"));
WicketUtils.setCssClass(owner, "label label-info");
item.add(owner);
break;
case TEAM:
Label team = new Label("pType", entry.source == null ? getString("gb.team") : entry.source);
WicketUtils.setHtmlTooltip(team, MessageFormat.format(getString("gb.teamPermission"), entry.source));
WicketUtils.setCssClass(team, "label label-success");
item.add(team);
break;
case REGEX:
Label regex = new Label("pType", "regex");
if (!StringUtils.isEmpty(entry.source)) {
WicketUtils.setHtmlTooltip(regex, MessageFormat.format(getString("gb.regexPermission"), entry.source));
}
WicketUtils.setCssClass(regex, "label");
item.add(regex);
break;
default:
if (entry.isMissing()) {
// repository is missing, this permission will be removed on save
Label missing = new Label("pType", getString("gb.missing"));
WicketUtils.setCssClass(missing, "label label-important");
WicketUtils.setHtmlTooltip(missing, getString("gb.missingPermission"));
item.add(missing);
} else {
// standard permission
item.add(new Label("pType", "").setVisible(false));
}
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
final DropDownChoice<AccessPermission> permissionChoice = new DropDownChoice<AccessPermission>(
"permission", Arrays.asList(AccessPermission.values()), new AccessPermissionRenderer(translations));
// only allow changing an explicitly defined permission
// this is designed to prevent changing a regex permission in
// a repository
permissionChoice.setEnabled(entry.mutable);
permissionChoice.setOutputMarkupId(true);
if (entry.mutable) {
permissionChoice.add(new AjaxFormComponentUpdatingBehavior("onchange") {
private static final long serialVersionUID = 1L;
@Override
protected void onUpdate(AjaxRequestTarget target) {
target.addComponent(permissionChoice);
}
});
}
item.add(permissionChoice);
}
};
add(dataView);
setOutputMarkupId(true);
// filter out registrants we already have permissions for
final List<String> registrants = new ArrayList<String>(allRegistrants);
for (RegistrantAccessPermission rp : permissions) {
if (rp.mutable) {
// remove editable duplicates
// this allows for specifying an explicit permission
registrants.remove(rp.registrant);
} else if (rp.isAdmin()) {
// administrators can not have their permission changed
registrants.remove(rp.registrant);
} else if (rp.isOwner()) {
// owners can not have their permission changed
registrants.remove(rp.registrant);
}
}
/*
* 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));
addPermissionForm.add(new DropDownChoice<AccessPermission>("permission", Arrays
.asList(AccessPermission.NEWPERMISSIONS), new AccessPermissionRenderer(translations)));
AjaxButton button = new AjaxButton("addPermissionButton", addPermissionForm) {
private static final long serialVersionUID = 1L;
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
// add permission to our list
RegistrantAccessPermission rp = (RegistrantAccessPermission) form.getModel().getObject();
if (rp.permission == null) {
return;
}
if (rp.registrant == null) {
return;
}
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);
// force the panel to refresh
target.addComponent(RegistrantPermissionsPanel.this);
}
};
addPermissionForm.add(button);
// only show add permission form if we have a registrant choice
add(addPermissionForm.setVisible(registrants.size() > 0));
}
@Override
protected boolean getStatelessHint()
{
return false;
}
private class AccessPermissionRenderer implements IChoiceRenderer<AccessPermission> {
private static final long serialVersionUID = 1L;
private final Map<AccessPermission, String> map;
public AccessPermissionRenderer(Map<AccessPermission, String> map) {
this.map = map;
}
@Override
public String getDisplayValue(AccessPermission type) {
return map.get(type);
}
@Override
public String getIdValue(AccessPermission type, int index) {
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);
}
};
}