/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.security.web.user;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.PasswordTextField;
import org.apache.wicket.markup.html.form.SubmitLink;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.form.validation.AbstractFormValidator;
import org.apache.wicket.markup.html.form.validation.EqualInputValidator;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.model.util.ListModel;
import org.geoserver.security.GeoServerRoleService;
import org.geoserver.security.GeoServerUserGroupService;
import org.geoserver.security.impl.GeoServerRole;
import org.geoserver.security.impl.GeoServerUser;
import org.geoserver.security.impl.GeoServerUserGroup;
import org.geoserver.security.impl.GroupAdminProperty;
import org.geoserver.security.impl.RoleCalculator;
import org.geoserver.security.password.GeoServerEmptyPasswordEncoder;
import org.geoserver.security.validation.AbstractSecurityException;
import org.geoserver.security.validation.PasswordPolicyException;
import org.geoserver.security.web.AbstractSecurityPage;
import org.geoserver.security.web.role.EditRolePage;
import org.geoserver.security.web.role.RoleListProvider;
import org.geoserver.security.web.role.RolePaletteFormComponent;
import org.geoserver.web.wicket.ParamResourceModel;
import org.geoserver.web.wicket.SimpleAjaxLink;
import org.geoserver.web.wicket.property.PropertyEditorFormComponent;
/**
* Allows creation of a new user in users.properties
*/
public abstract class AbstractUserPage extends AbstractSecurityPage {
protected RolePaletteFormComponent rolePalette;
protected UserGroupPaletteFormComponent userGroupPalette;
protected UserGroupListMultipleChoice adminGroupChoice;
protected ListView<GeoServerRole> calculatedRoles;
protected String ugServiceName;
protected AbstractUserPage(String ugServiceName, final GeoServerUser user) {
this.ugServiceName=ugServiceName;
GeoServerUserGroupService ugService = getUserGroupService(ugServiceName);
boolean emptyPasswd = getSecurityManager().loadPasswordEncoder(ugService.getPasswordEncoderName())
instanceof GeoServerEmptyPasswordEncoder;
boolean hasUserGroupStore = ugService.canCreateStore();
boolean hasRoleStore = hasRoleStore(getSecurityManager().getActiveRoleService().getName());
// build the form
Form form = new Form<Serializable>("form", new CompoundPropertyModel(user));
add(form);
form.add(new TextField("username").setEnabled(hasUserGroupStore));
form.add(new CheckBox("enabled").setEnabled(hasUserGroupStore));
PasswordTextField pw1 = new PasswordTextField("password") {
@Override
public boolean isRequired() {
return isFinalSubmit(this);
}
};
form.add(pw1);
pw1.setResetPassword(false);
pw1.setEnabled(hasUserGroupStore && !emptyPasswd);
PasswordTextField pw2 = new PasswordTextField("confirmPassword",
new Model(user.getPassword())) {
@Override
public boolean isRequired() {
return isFinalSubmit(this);
}
};
form.add(pw2);
pw2.setResetPassword(false);
pw2.setEnabled(hasUserGroupStore && !emptyPasswd);
form.add(new PropertyEditorFormComponent("properties").setEnabled(hasUserGroupStore));
form.add(userGroupPalette = new UserGroupPaletteFormComponent("groups", ugServiceName, user));
userGroupPalette.add(new AjaxFormComponentUpdatingBehavior("change") {
@Override
protected void onUpdate(AjaxRequestTarget target) {
updateCalculatedRoles(target);
}
});
userGroupPalette.setEnabled(hasUserGroupStore);
List<GeoServerRole> roles;
try {
roles = new ArrayList(
getSecurityManager().getActiveRoleService().getRolesForUser(user.getUsername()));
} catch (IOException e) {
throw new WicketRuntimeException(e);
}
form.add(rolePalette = new RolePaletteFormComponent("roles", new ListModel(roles)));
rolePalette.add(new AjaxFormComponentUpdatingBehavior("change") {
@Override
protected void onUpdate(AjaxRequestTarget target) {
updateCalculatedRoles(target);
updateGroupAdminList(target);
}
});
rolePalette.setOutputMarkupId(true);
rolePalette.setEnabled(hasRoleStore);
boolean isGroupAdmin = roles.contains(GeoServerRole.GROUP_ADMIN_ROLE);
List<GeoServerUserGroup> adminGroups = new ArrayList();
if (isGroupAdmin) {
for (String groupName : GroupAdminProperty.get(user.getProperties())) {
try {
adminGroups.add(ugService.getGroupByGroupname(groupName));
} catch (IOException e) {
throw new WicketRuntimeException(e);
}
}
}
form.add(adminGroupChoice = new UserGroupListMultipleChoice("adminGroups",
new ListModel(adminGroups), new GroupsModel(ugServiceName)));
adminGroupChoice.setOutputMarkupId(true);
adminGroupChoice.setEnabled(hasUserGroupStore && isGroupAdmin);
WebMarkupContainer container = new WebMarkupContainer("calculatedRolesContainer");
form.add(container);
container.setOutputMarkupId(true);
container.add(calculatedRoles =
new ListView<GeoServerRole>("calculatedRoles", new CalculatedRoleModel(user)) {
@Override
@SuppressWarnings("unchecked")
protected void populateItem(ListItem<GeoServerRole> item) {
IModel<GeoServerRole> model = item.getModel();
item.add(new SimpleAjaxLink("role", model, RoleListProvider.ROLENAME.getModel(model)) {
@Override
protected void onClick(AjaxRequestTarget target) {
setResponsePage(
new EditRolePage(getSecurityManager().getActiveRoleService().getName(),
(GeoServerRole) getDefaultModelObject()).setReturnPage(this.getPage()));
}
});
}
});
calculatedRoles.setOutputMarkupId(true);
form.add(new SubmitLink("save") {
@Override
public void onSubmit() {
try {
//update the user property listing the group names the user is admin for
if (adminGroupChoice.isEnabled()) {
Collection<GeoServerUserGroup> groups = adminGroupChoice.getModelObject();
String[] groupNames = new String[groups.size()];
int i = 0;
for (GeoServerUserGroup group : groups) {
groupNames[i++] = group.getGroupname();
}
GroupAdminProperty.set(user.getProperties(), groupNames);
}
else {
GroupAdminProperty.del(user.getProperties());
}
onFormSubmit(user);
setReturnPageDirtyAndReturn(true);
}
catch(Exception e) {
handleSubmitError(e);
}
}
}.setEnabled(hasUserGroupStore || hasRoleStore(getSecurityManager().getActiveRoleService().getName())));
form.add(getCancelLink());
// add the validators
form.add(new EqualInputValidator(pw1, pw2) {
private static final long serialVersionUID = 1L;
@Override
public void validate(Form<?> form) {
if (isFinalSubmit(form)) {
super.validate(form);
}
}
@Override
protected String resourceKey() {
return "AbstractUserPage.passwordMismatch";
}
});
form.add(new GroupAdminValidator());
}
boolean isFinalSubmit(FormComponent component) {
return isFinalSubmit(Form.findForm(component));
}
boolean isFinalSubmit(Form form) {
if (form == null) {
return false;
}
return form.findSubmittingButton() == form.get("save");
}
void updateCalculatedRoles(AjaxRequestTarget target) {
calculatedRoles.modelChanged();
target.add(calculatedRoles.getParent());
}
void updateGroupAdminList(AjaxRequestTarget target) {
adminGroupChoice.setEnabled(
rolePalette.getSelectedRoles().contains(GeoServerRole.GROUP_ADMIN_ROLE));
target.add(adminGroupChoice);
}
void handleSubmitError(Exception e) {
LOGGER.log(Level.SEVERE, "Error occurred while saving user", e);
if (e instanceof RuntimeException && e.getCause() instanceof Exception) {
e = (Exception) e.getCause();
}
if (e instanceof IOException && e.getCause() instanceof AbstractSecurityException) {
e = (Exception) e.getCause();
}
if (e instanceof AbstractSecurityException) {
error(e);
}
else {
error(new ParamResourceModel("saveError", getPage(), e.getMessage()).getObject());
}
}
/**
* List model that calculates derived roles for the user, those assigned directly and through
* group membership.
*/
class CalculatedRoleModel extends LoadableDetachableModel<List<GeoServerRole>> {
GeoServerUser user;
CalculatedRoleModel(GeoServerUser user) {
this.user = user;
}
@Override
protected List<GeoServerRole> load() {
List<GeoServerRole> tmp = new ArrayList<GeoServerRole>();
List<GeoServerRole> result = new ArrayList<GeoServerRole>();
try {
GeoServerUserGroupService ugService = getSecurityManager()
.loadUserGroupService(ugServiceName);
GeoServerRoleService gaService = getSecurityManager()
.getActiveRoleService();
RoleCalculator calc = new RoleCalculator(ugService, gaService);
tmp.addAll(rolePalette.getSelectedRoles());
calc.addInheritedRoles(tmp);
for (GeoServerUserGroup group : userGroupPalette.getSelectedGroups()) {
if (group.isEnabled()) {
tmp.addAll(calc.calculateRoles(group));
}
}
result.addAll(calc.personalizeRoles(user, tmp));
} catch (IOException e) {
throw new RuntimeException(e);
}
Collections.sort(result);
return result;
}
}
/**
* Validator that ensures when a user is assigned to be a group admin that at least one group
* is selected.
*/
class GroupAdminValidator extends AbstractFormValidator {
@Override
public FormComponent<?>[] getDependentFormComponents() {
return new FormComponent[] {adminGroupChoice};
}
@Override
public void validate(Form<?> form) {
if (adminGroupChoice.isEnabled()) {
adminGroupChoice.updateModel();
if (adminGroupChoice.getModelObject().isEmpty()) {
form.error(new StringResourceModel("noAdminGroups", getPage(), null).getString());
}
}
}
}
/**
* Implements the actual save action.
*/
protected abstract void onFormSubmit(GeoServerUser user)
throws IOException, PasswordPolicyException;
}