You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by fm...@apache.org on 2016/03/16 17:47:38 UTC

syncope git commit: [SYNCOPE-778] providing feature to force change password (bulk as well)

Repository: syncope
Updated Branches:
  refs/heads/master 9a10f6e99 -> 04183d50f


[SYNCOPE-778] providing feature to force change password (bulk as well)


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/04183d50
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/04183d50
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/04183d50

Branch: refs/heads/master
Commit: 04183d50fe8bd2e8f9e94a2e115a91c7bd4afac3
Parents: 9a10f6e
Author: fmartelli <fa...@gmail.com>
Authored: Wed Mar 16 17:47:23 2016 +0100
Committer: fmartelli <fa...@gmail.com>
Committed: Wed Mar 16 17:47:23 2016 +0100

----------------------------------------------------------------------
 .../client/console/bulk/BulkContent.java        | 13 +--
 .../panels/AbstractSearchResultPanel.java       |  8 +-
 .../console/panels/AjaxDataTablePanel.java      |  7 +-
 .../panels/UserDisplayAttributesModalPanel.java |  2 +-
 .../console/panels/UserSearchResultPanel.java   | 97 +++++++++++++-------
 .../client/console/rest/UserRestClient.java     |  8 ++
 .../wicket/markup/html/form/ActionLink.java     |  1 +
 .../markup/html/form/ActionLinksPanel.java      | 24 +++++
 .../markup/html/form/ActionLinksPanel.html      |  5 +
 .../syncope/common/lib/to/BulkAction.java       |  1 +
 .../rest/cxf/service/AbstractAnyService.java    | 58 +++++++++---
 .../syncope/fit/console/BulkActionITCase.java   |  6 +-
 12 files changed, 165 insertions(+), 65 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/04183d50/client/console/src/main/java/org/apache/syncope/client/console/bulk/BulkContent.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/bulk/BulkContent.java b/client/console/src/main/java/org/apache/syncope/client/console/bulk/BulkContent.java
index 153c39c..caf5dce 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/bulk/BulkContent.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/bulk/BulkContent.java
@@ -29,7 +29,6 @@ import java.util.Map;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.commons.Constants;
 import org.apache.syncope.client.console.commons.status.StatusBean;
-import org.apache.syncope.client.console.panels.AbstractSearchResultPanel.EventDataWrapper;
 import org.apache.syncope.client.console.panels.MultilevelPanel;
 import org.apache.syncope.client.console.rest.AbstractAnyRestClient;
 import org.apache.syncope.client.console.rest.BaseRestClient;
@@ -44,7 +43,6 @@ import org.apache.syncope.common.lib.types.ResourceAssociationAction;
 import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
 import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.wicket.ajax.AjaxRequestTarget;
-import org.apache.wicket.event.Broadcast;
 import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackDefaultDataTable;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
@@ -69,8 +67,7 @@ public class BulkContent<T extends Serializable, S> extends MultilevelPanel.Seco
             final BaseRestClient bulkActionExecutor,
             final String keyFieldName) {
 
-        this(MultilevelPanel.SECOND_LEVEL_ID,
-                modal, items, columns, actions, bulkActionExecutor, keyFieldName);
+        this(MultilevelPanel.SECOND_LEVEL_ID, modal, items, columns, actions, bulkActionExecutor, keyFieldName);
     }
 
     public BulkContent(
@@ -146,8 +143,8 @@ public class BulkContent<T extends Serializable, S> extends MultilevelPanel.Seco
                                 throw new IllegalArgumentException("Invalid bulk action executor");
                             }
 
-                            final AbstractAnyRestClient<?> anyRestClient =
-                                    AbstractAnyRestClient.class.cast(bulkActionExecutor);
+                            final AbstractAnyRestClient<?> anyRestClient
+                                    = AbstractAnyRestClient.class.cast(bulkActionExecutor);
 
                             if (items.isEmpty() || !(items.iterator().next() instanceof StatusBean)) {
                                 throw new IllegalArgumentException("Invalid items");
@@ -205,10 +202,6 @@ public class BulkContent<T extends Serializable, S> extends MultilevelPanel.Seco
                             modal.changeCloseButtonLabel(getString("close", null, "Close"), target);
                         }
 
-                        final EventDataWrapper data = new EventDataWrapper();
-                        data.setTarget(target);
-                        send(getPage(), Broadcast.BREADTH, data);
-
                         final List<IColumn<T, S>> newColumnList = new ArrayList<>(columns);
                         newColumnList.add(newColumnList.size(), new BulkActionResultColumn<T, S>(res, keyFieldName));
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/04183d50/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSearchResultPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSearchResultPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSearchResultPanel.java
index e2a6686..cb5221a 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSearchResultPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSearchResultPanel.java
@@ -143,8 +143,10 @@ public abstract class AbstractSearchResultPanel<
         rows = prefMan.getPaginatorRows(getRequest(), paginatorRowsKey());
 
         setWindowClosedReloadCallback(modal);
+        setWindowClosedReloadCallback(altDefaultModal);
+        setWindowClosedReloadCallback(displayAttributeModal);
     }
-    
+
     protected abstract DP dataProvider();
 
     protected abstract String paginatorRowsKey();
@@ -247,7 +249,9 @@ public abstract class AbstractSearchResultPanel<
                 updateResultTable(data.isCreate(), data.getRows());
             }
 
-            data.getTarget().add(container);
+            if (AbstractSearchResultPanel.this.container.isVisibleInHierarchy()) {
+                data.getTarget().add(AbstractSearchResultPanel.this.container);
+            }
         }
         super.onEvent(event);
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/04183d50/client/console/src/main/java/org/apache/syncope/client/console/panels/AjaxDataTablePanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AjaxDataTablePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AjaxDataTablePanel.java
index d02a784..9662753 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AjaxDataTablePanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AjaxDataTablePanel.java
@@ -21,6 +21,7 @@ package org.apache.syncope.client.console.panels;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.rest.BaseRestClient;
@@ -214,7 +215,7 @@ public final class AjaxDataTablePanel<T extends Serializable, S> extends DataTab
                     target.add(bulkModal.setContent(new BulkActionModal<>(
                             bulkModal,
                             builder.pageRef,
-                            group.getModelObject(),
+                            new ArrayList<T>(group.getModelObject()),
                             // serialization problem with sublist only
                             new ArrayList<>(builder.columns.subList(1, builder.columns.size() - 1)),
                             builder.bulkActions,
@@ -225,13 +226,15 @@ public final class AjaxDataTablePanel<T extends Serializable, S> extends DataTab
                 } else {
                     builder.multiLevelPanel.next("bulk.action", new BulkContent<>(
                             builder.baseModal,
-                            group.getModelObject(),
+                            new ArrayList<T>(group.getModelObject()),
                             // serialization problem with sublist only
                             new ArrayList<>(builder.columns.subList(1, builder.columns.size() - 1)),
                             builder.bulkActions,
                             builder.bulkActionExecutor,
                             builder.itemKeyField), target);
                 }
+                group.setModelObject(Collections.<T>emptyList());
+                target.add(group);
             }
         }.setEnabled(builder.isBulkEnabled()).setVisible(builder.isBulkEnabled()));
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/04183d50/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDisplayAttributesModalPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDisplayAttributesModalPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDisplayAttributesModalPanel.java
index baf5f4e..c8022e3 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDisplayAttributesModalPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDisplayAttributesModalPanel.java
@@ -35,7 +35,7 @@ public class UserDisplayAttributesModalPanel<T extends Serializable> extends Dis
 
     private static final long serialVersionUID = 5194630813773543054L;
 
-    public static final String[] USER_DEFAULT_SELECTION = { "key", "username", "status" };
+    public static final String[] USER_DEFAULT_SELECTION = { "key", "username", "status", "mustChangePassword" };
 
     public UserDisplayAttributesModalPanel(
             final BaseModal<T> modal,

http://git-wip-us.apache.org/repos/asf/syncope/blob/04183d50/client/console/src/main/java/org/apache/syncope/client/console/panels/UserSearchResultPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/UserSearchResultPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/UserSearchResultPanel.java
index 7cb3bb5..443d01e 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/UserSearchResultPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/UserSearchResultPanel.java
@@ -22,7 +22,7 @@ import java.io.Serializable;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Date;
+import java.util.Collection;
 import java.util.List;
 import org.apache.commons.lang3.SerializationUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -32,6 +32,7 @@ import org.apache.syncope.client.console.rest.UserRestClient;
 import org.apache.syncope.client.console.status.StatusModal;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.ActionColumn;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.AttrColumn;
+import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksPanel;
 import org.apache.syncope.client.console.wizards.AjaxWizard;
@@ -67,6 +68,18 @@ public class UserSearchResultPanel extends AnySearchResultPanel<UserTO> {
     }
 
     @Override
+    protected Collection<ActionLink.ActionType> getBulkActions() {
+        final List<ActionLink.ActionType> bulkActions = new ArrayList<>();
+
+        bulkActions.add(ActionLink.ActionType.MUSTCHANGEPASSWORD);
+        bulkActions.add(ActionLink.ActionType.DELETE);
+        bulkActions.add(ActionLink.ActionType.SUSPEND);
+        bulkActions.add(ActionLink.ActionType.REACTIVATE);
+
+        return bulkActions;
+    }
+
+    @Override
     protected List<IColumn<UserTO, String>> getColumns() {
 
         final List<IColumn<UserTO, String>> columns = new ArrayList<>();
@@ -74,10 +87,8 @@ public class UserSearchResultPanel extends AnySearchResultPanel<UserTO> {
         for (String name : prefMan.getList(getRequest(), Constants.PREF_USERS_DETAILS_VIEW)) {
             final Field field = ReflectionUtils.findField(UserTO.class, name);
 
-            if ("token".equalsIgnoreCase(name)) {
-                columns.add(new PropertyColumn<UserTO, String>(new ResourceModel(name, name), name, name));
-            } else if (field != null && field.getType().equals(Date.class)) {
-                columns.add(new PropertyColumn<UserTO, String>(new ResourceModel(name, name), name, name));
+            if (field != null && (field.getType().equals(Boolean.class) || field.getType().equals(boolean.class))) {
+                columns.add(new BooleanPropertyColumn<UserTO>(new ResourceModel(name, name), name, name));
             } else {
                 columns.add(new PropertyColumn<UserTO, String>(new ResourceModel(name, name), name, name));
             }
@@ -121,49 +132,69 @@ public class UserSearchResultPanel extends AnySearchResultPanel<UserTO> {
 
                     @Override
                     public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
-
-                        final IModel<AnyHandler<UserTO>> formModel =
-                                new CompoundPropertyModel<>(new AnyHandler<>(model.getObject()));
-                        altDefaultModal.setFormModel(formModel);
-
-                        target.add(altDefaultModal.setContent(new StatusModal<>(
-                                altDefaultModal, pageRef, formModel.getObject().getInnerObject(), false)));
-
-                        altDefaultModal.header(new Model<>(
-                                getString("any.edit", new Model<>(new AnyHandler<>(model.getObject())))));
-
-                        altDefaultModal.show(true);
+                        try {
+                            UserRestClient.class.cast(restClient).
+                                    mustChangePassword(model.getObject().getETagValue(), model.getObject().getKey());
+                            info(getString(Constants.OPERATION_SUCCEEDED));
+                            target.add(container);
+                        } catch (SyncopeClientException e) {
+                            LOG.error("While deleting object {}", model.getObject().getKey(), e);
+                            error(StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage());
+                        }
+                        SyncopeConsoleSession.get().getNotificationPanel().refresh(target);
                     }
-                }, ActionLink.ActionType.MANAGE_RESOURCES, StandardEntitlement.USER_READ).add(new ActionLink<UserTO>() {
+                }, ActionLink.ActionType.MUSTCHANGEPASSWORD, StandardEntitlement.USER_UPDATE).add(
+                        new ActionLink<UserTO>() {
 
                     private static final long serialVersionUID = -7978723352517770644L;
 
                     @Override
                     public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
-                        final IModel<AnyHandler<UserTO>> formModel =
-                                new CompoundPropertyModel<>(new AnyHandler<>(model.getObject()));
+
+                        final IModel<AnyHandler<UserTO>> formModel
+                                = new CompoundPropertyModel<>(new AnyHandler<>(model.getObject()));
                         altDefaultModal.setFormModel(formModel);
 
                         target.add(altDefaultModal.setContent(new StatusModal<>(
-                                altDefaultModal, pageRef, formModel.getObject().getInnerObject(), true)));
+                                altDefaultModal, pageRef, formModel.getObject().getInnerObject(), false)));
 
                         altDefaultModal.header(new Model<>(
                                 getString("any.edit", new Model<>(new AnyHandler<>(model.getObject())))));
 
                         altDefaultModal.show(true);
                     }
-                }, ActionLink.ActionType.ENABLE, StandardEntitlement.USER_READ).add(new ActionLink<UserTO>() {
-
-                    private static final long serialVersionUID = -7978723352517770644L;
-
-                    @Override
-                    public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
-                        send(UserSearchResultPanel.this, Broadcast.EXACT,
-                                new AjaxWizard.EditItemActionEvent<>(
-                                        new AnyHandler<>(new UserRestClient().read(model.getObject().getKey())),
-                                        target));
-                    }
-                }, ActionLink.ActionType.EDIT, StandardEntitlement.USER_READ).add(new ActionLink<UserTO>() {
+                }, ActionLink.ActionType.MANAGE_RESOURCES, StandardEntitlement.USER_READ).add(
+                                new ActionLink<UserTO>() {
+
+                            private static final long serialVersionUID = -7978723352517770644L;
+
+                            @Override
+                            public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
+                                final IModel<AnyHandler<UserTO>> formModel = new CompoundPropertyModel<>(
+                                        new AnyHandler<>(model.
+                                                getObject()));
+                                altDefaultModal.setFormModel(formModel);
+
+                                target.add(altDefaultModal.setContent(new StatusModal<>(
+                                        altDefaultModal, pageRef, formModel.getObject().getInnerObject(), true)));
+
+                                altDefaultModal.header(new Model<>(
+                                        getString("any.edit", new Model<>(new AnyHandler<>(model.getObject())))));
+
+                                altDefaultModal.show(true);
+                            }
+                        }, ActionLink.ActionType.ENABLE, StandardEntitlement.USER_READ).add(new ActionLink<UserTO>() {
+
+                            private static final long serialVersionUID = -7978723352517770644L;
+
+                            @Override
+                            public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
+                                send(UserSearchResultPanel.this, Broadcast.EXACT,
+                                        new AjaxWizard.EditItemActionEvent<>(
+                                                new AnyHandler<>(new UserRestClient().read(model.getObject().getKey())),
+                                                target));
+                            }
+                        }, ActionLink.ActionType.EDIT, StandardEntitlement.USER_READ).add(new ActionLink<UserTO>() {
 
                     private static final long serialVersionUID = -7978723352517770644L;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/04183d50/client/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java
index ff4b2fa..b26d31b 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java
@@ -23,6 +23,7 @@ import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.Response;
 import org.apache.syncope.client.console.commons.status.StatusBean;
 import org.apache.syncope.client.console.commons.status.StatusUtils;
+import org.apache.syncope.common.lib.patch.BooleanReplacePatchItem;
 import org.apache.syncope.common.lib.patch.StatusPatch;
 import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.to.BulkAction;
@@ -115,6 +116,13 @@ public class UserRestClient extends AbstractAnyRestClient<UserTO> {
         return getService(ResourceService.class).readConnObject(resourceName, AnyTypeKind.USER.name(), id);
     }
 
+    public ProvisioningResult<UserTO> mustChangePassword(final String etag, final Long key) {
+        final UserPatch userPatch = new UserPatch();
+        userPatch.setKey(key);
+        userPatch.setMustChangePassword(new BooleanReplacePatchItem.Builder().value(true).build());
+        return update(etag, userPatch);
+    }
+
     public void suspend(final String etag, final long userKey, final List<StatusBean> statuses) {
         StatusPatch statusPatch = StatusUtils.buildStatusPatch(statuses, false);
         statusPatch.setKey(userKey);

http://git-wip-us.apache.org/repos/asf/syncope/blob/04183d50/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
index 93fda56..9bd19ee 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
@@ -42,6 +42,7 @@ public abstract class ActionLink<T extends Serializable> implements Serializable
 
         MAPPING("update"),
         ACCOUNT_LINK("update"),
+        MUSTCHANGEPASSWORD("update"),
         RESET_TIME("update"),
         CLONE("create"),
         CREATE("create"),

http://git-wip-us.apache.org/repos/asf/syncope/blob/04183d50/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksPanel.java
index b56da38..4170ba9 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksPanel.java
@@ -57,6 +57,7 @@ public final class ActionLinksPanel<T extends Serializable> extends Panel {
         super.add(new Fragment("panelManageGroups", "emptyFragment", this));
         super.add(new Fragment("panelMapping", "emptyFragment", this));
         super.add(new Fragment("panelAccountLink", "emptyFragment", this));
+        super.add(new Fragment("panelResetPwd", "emptyFragment", this));
         super.add(new Fragment("panelResetTime", "emptyFragment", this));
         super.add(new Fragment("panelClone", "emptyFragment", this));
         super.add(new Fragment("panelCreate", "emptyFragment", this));
@@ -212,6 +213,25 @@ public final class ActionLinksPanel<T extends Serializable> extends Panel {
                 }.setVisible(link.isEnabled(model.getObject())));
                 break;
 
+            case MUSTCHANGEPASSWORD:
+                fragment = new Fragment("panelResetPwd", "fragmentResetPwd", this);
+
+                fragment.addOrReplace(new IndicatingAjaxLink<Void>("resetPwdLink") {
+
+                    private static final long serialVersionUID = -7978723352517770644L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        link.onClick(target, model.getObject());
+                    }
+
+                    @Override
+                    public String getAjaxIndicatorMarkupId() {
+                        return disableIndicator ? StringUtils.EMPTY : super.getAjaxIndicatorMarkupId();
+                    }
+                }.setVisible(link.isEnabled(model.getObject())));
+                break;
+
             case RESET_TIME:
                 fragment = new Fragment("panelResetTime", "fragmentResetTime", this);
 
@@ -792,6 +812,10 @@ public final class ActionLinksPanel<T extends Serializable> extends Panel {
                 super.addOrReplace(new Fragment("panelAccountLink", "emptyFragment", this));
                 break;
 
+            case MUSTCHANGEPASSWORD:
+                super.addOrReplace(new Fragment("panelResetPwd", "emptyFragment", this));
+                break;
+
             case RESET_TIME:
                 super.addOrReplace(new Fragment("panelResetTime", "emptyFragment", this));
                 break;

http://git-wip-us.apache.org/repos/asf/syncope/blob/04183d50/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksPanel.html
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksPanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksPanel.html
index 78d0927..d8754e6 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksPanel.html
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksPanel.html
@@ -26,6 +26,7 @@ under the License.
     </style>
   </wicket:head>
   <wicket:panel>
+    <span wicket:id="panelResetPwd">[plus]</span>
     <span wicket:id="panelClaim">[plus]</span>
     <span wicket:id="panelManageResources">[plus]</span>
     <span wicket:id="panelManageUsers">[plus]</span>
@@ -62,6 +63,10 @@ under the License.
     <span wicket:id="panelZoomIn">[plus]</span>
     <span wicket:id="panelZoomOut">[plus]</span>
 
+    <wicket:fragment wicket:id="fragmentResetPwd">
+      <a href="#" wicket:id="resetPwdLink" class="btn"><i class="fa fa-lock" alt="reset password icon" title="Reset password"></i></a>
+    </wicket:fragment>
+
     <wicket:fragment wicket:id="fragmentClaim">
       <a href="#" wicket:id="claimLink" class="btn"><img id="actionLink" src="img/actions/claim.png" alt="claim icon" title="Claim"/></a>
     </wicket:fragment>

http://git-wip-us.apache.org/repos/asf/syncope/blob/04183d50/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkAction.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkAction.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkAction.java
index 2fad211..c3edf7c 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkAction.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkAction.java
@@ -38,6 +38,7 @@ public class BulkAction extends AbstractBaseBean {
     @XmlType(name = "bulkActionType")
     public enum Type {
 
+        MUSTCHANGEPASSWORD,
         DELETE,
         REACTIVATE,
         SUSPEND,

http://git-wip-us.apache.org/repos/asf/syncope/blob/04183d50/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
index ca324a8..98f9aae 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.rest.cxf.service;
 
 import java.util.Set;
+import javax.ws.rs.BadRequestException;
 import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.AnyOperations;
@@ -26,8 +27,10 @@ import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.patch.AnyPatch;
 import org.apache.syncope.common.lib.patch.AssociationPatch;
 import org.apache.syncope.common.lib.patch.AttrPatch;
+import org.apache.syncope.common.lib.patch.BooleanReplacePatchItem;
 import org.apache.syncope.common.lib.patch.DeassociationPatch;
 import org.apache.syncope.common.lib.patch.StatusPatch;
+import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.search.SpecialAttr;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.AttrTO;
@@ -197,8 +200,8 @@ public abstract class AbstractAnyService<TO extends AnyTO, P extends AnyPatch>
 
         checkETag(before.getETagValue());
 
-        ProvisioningResult<TO> updated =
-                getAnyLogic().update(AnyOperations.<TO, P>diff(anyTO, before, false), isNullPriorityAsync());
+        ProvisioningResult<TO> updated = getAnyLogic().update(AnyOperations.<TO, P>diff(anyTO, before, false),
+                isNullPriorityAsync());
         return modificationResponse(updated);
     }
 
@@ -326,6 +329,27 @@ public abstract class AbstractAnyService<TO extends AnyTO, P extends AnyPatch>
         BulkActionResult result = new BulkActionResult();
 
         switch (bulkAction.getType()) {
+            case MUSTCHANGEPASSWORD:
+                if (logic instanceof UserLogic) {
+                    for (String key : bulkAction.getTargets()) {
+                        try {
+                            final UserPatch userPatch = new UserPatch();
+                            userPatch.setKey(Long.valueOf(key));
+                            userPatch.setMustChangePassword(new BooleanReplacePatchItem.Builder().value(true).build());
+
+                            result.getResults().put(
+                                    String.valueOf(((UserLogic) logic).update(userPatch, false).getAny().getKey()),
+                                    BulkActionResult.Status.SUCCESS);
+                        } catch (Exception e) {
+                            LOG.error("Error performing delete for user {}", key, e);
+                            result.getResults().put(key, BulkActionResult.Status.FAILURE);
+                        }
+                    }
+                } else {
+                    throw new BadRequestException();
+                }
+                break;
+
             case DELETE:
                 for (String key : bulkAction.getTargets()) {
                     try {
@@ -356,23 +380,29 @@ public abstract class AbstractAnyService<TO extends AnyTO, P extends AnyPatch>
                             result.getResults().put(key, BulkActionResult.Status.FAILURE);
                         }
                     }
+                } else {
+                    throw new BadRequestException();
                 }
                 break;
 
             case REACTIVATE:
-                for (String key : bulkAction.getTargets()) {
-                    StatusPatch statusPatch = new StatusPatch();
-                    statusPatch.setKey(Long.valueOf(key));
-                    statusPatch.setType(StatusPatchType.REACTIVATE);
-                    try {
-                        result.getResults().put(
-                                String.valueOf(((UserLogic) logic).
-                                        status(statusPatch, isNullPriorityAsync()).getAny().getKey()),
-                                BulkActionResult.Status.SUCCESS);
-                    } catch (Exception e) {
-                        LOG.error("Error performing reactivate for user {}", key, e);
-                        result.getResults().put(key, BulkActionResult.Status.FAILURE);
+                if (logic instanceof UserLogic) {
+                    for (String key : bulkAction.getTargets()) {
+                        StatusPatch statusPatch = new StatusPatch();
+                        statusPatch.setKey(Long.valueOf(key));
+                        statusPatch.setType(StatusPatchType.REACTIVATE);
+                        try {
+                            result.getResults().put(
+                                    String.valueOf(((UserLogic) logic).
+                                            status(statusPatch, isNullPriorityAsync()).getAny().getKey()),
+                                    BulkActionResult.Status.SUCCESS);
+                        } catch (Exception e) {
+                            LOG.error("Error performing reactivate for user {}", key, e);
+                            result.getResults().put(key, BulkActionResult.Status.FAILURE);
+                        }
                     }
+                } else {
+                    throw new BadRequestException();
                 }
                 break;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/04183d50/fit/core-reference/src/test/java/org/apache/syncope/fit/console/BulkActionITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/BulkActionITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/BulkActionITCase.java
index b193672..33421ce 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/BulkActionITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/BulkActionITCase.java
@@ -77,7 +77,7 @@ public class BulkActionITCase extends AbstractConsoleITCase {
         assertNotNull(component);
 
         wicketTester.clickLink(component.getPageRelativePath()
-                + ":cells:5:cell:panelManageResources:manageResourcesLink");
+                + ":cells:6:cell:panelManageResources:manageResourcesLink");
 
         wicketTester.assertComponent(tabPanel + "alternativeDefaultModal:form:content:status:"
                 + "firstLevelContainer:first:container:content:searchContainer:resultTable:tablePanel:groupForm:"
@@ -118,7 +118,7 @@ public class BulkActionITCase extends AbstractConsoleITCase {
                 + ":searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable", "rossini");
         assertNotNull(component);
 
-        wicketTester.clickLink(component.getPageRelativePath() + ":cells:5:cell:panelEnable:enableLink");
+        wicketTester.clickLink(component.getPageRelativePath() + ":cells:6:cell:panelEnable:enableLink");
 
         wicketTester.assertComponent(tabPanel + "alternativeDefaultModal:form:content:status:"
                 + "firstLevelContainer:first:container:content:searchContainer:resultTable:tablePanel:groupForm:"
@@ -153,7 +153,7 @@ public class BulkActionITCase extends AbstractConsoleITCase {
                 + ":searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable", "rossini");
         assertNotNull(component);
 
-        wicketTester.clickLink(component.getPageRelativePath() + ":cells:5:cell:panelEnable:enableLink");
+        wicketTester.clickLink(component.getPageRelativePath() + ":cells:6:cell:panelEnable:enableLink");
 
         wicketTester.assertComponent(tabPanel + "alternativeDefaultModal:form:content:status:"
                 + "firstLevelContainer:first:container:content:searchContainer:resultTable:tablePanel:groupForm:"