blob: e45775efa40611f44482d08680ddb8662a74de41 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.syncope.client.console.audit;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.client.console.SyncopeConsoleSession;
import org.apache.syncope.client.console.audit.AuditHistoryDirectoryPanel.AuditHistoryProvider;
import org.apache.syncope.client.console.commons.IdRepoConstants;
import org.apache.syncope.client.console.pages.BasePage;
import org.apache.syncope.client.console.panels.AjaxDataTablePanel;
import org.apache.syncope.client.console.panels.DirectoryPanel;
import org.apache.syncope.client.console.panels.MultilevelPanel;
import org.apache.syncope.client.console.rest.AnyObjectRestClient;
import org.apache.syncope.client.console.rest.AuditHistoryRestClient;
import org.apache.syncope.client.console.rest.GroupRestClient;
import org.apache.syncope.client.console.rest.UserRestClient;
import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn;
import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
import org.apache.syncope.client.ui.commons.DirectoryDataProvider;
import org.apache.syncope.client.ui.commons.panels.ModalPanel;
import org.apache.syncope.common.lib.AnyOperations;
import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.request.AnyObjectUR;
import org.apache.syncope.common.lib.request.GroupUR;
import org.apache.syncope.common.lib.request.UserUR;
import org.apache.syncope.common.lib.to.AnyObjectTO;
import org.apache.syncope.common.lib.to.AnyTO;
import org.apache.syncope.common.lib.to.AuditEntryTO;
import org.apache.syncope.common.lib.to.GroupTO;
import org.apache.syncope.common.lib.to.ProvisioningResult;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.AuditElements;
import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.lib.types.IdRepoEntitlement;
import org.apache.wicket.PageReference;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.StringResourceModel;
public class AuditHistoryDirectoryPanel extends
DirectoryPanel<AuditEntryTO, AuditEntryTO, AuditHistoryProvider, AuditHistoryRestClient>
implements ModalPanel {
private static final long serialVersionUID = -8248734710505211261L;
private static final int TOTAL_AUDIT_HISTORY_COMPARISONS = 25;
private static final ObjectMapper MAPPER = new ObjectMapper();
private final BaseModal<?> baseModal;
private final MultilevelPanel multiLevelPanelRef;
private final AnyTO anyTO;
private final AnyTypeKind anyTypeKind;
public AuditHistoryDirectoryPanel(
final BaseModal<?> baseModal,
final MultilevelPanel multiLevelPanelRef,
final PageReference pageRef,
final AnyTO anyTO) {
super(MultilevelPanel.FIRST_LEVEL_ID, pageRef);
disableCheckBoxes();
this.baseModal = baseModal;
this.multiLevelPanelRef = multiLevelPanelRef;
this.anyTO = anyTO;
anyTypeKind = AnyTypeKind.fromTOClass(anyTO.getClass());
initResultTable();
}
/**
* Restore an object based on the audit record.
*
* Note that for user objects, the original audit record masks
* the password and the security answer; so we cannot use the audit
* record to resurrect the entry based on mask data. The method behavior
* below will reset the audit record such that the current security answer
* and the password for the object are always maintained, and such properties
* for the user cannot be restored using audit records.
*
* @param entryBean the entry bean
* @param anyTO the any to
* @return the response
*/
private static ProvisioningResult<? extends AnyTO> restore(final AuditEntryTO entryBean,
final AnyTO anyTO) {
try {
String json = getJSONFromAuditEntry(entryBean);
if (anyTO instanceof UserTO) {
UserTO userTO = MAPPER.readValue(json, UserTO.class);
UserUR req = AnyOperations.diff(userTO, anyTO, false);
req.setPassword(null);
req.setSecurityAnswer(null);
return new UserRestClient().update(anyTO.getETagValue(), req);
}
if (anyTO instanceof GroupTO) {
GroupTO groupTO = MAPPER.readValue(json, GroupTO.class);
GroupUR req = AnyOperations.diff(groupTO, anyTO, false);
return new GroupRestClient().update(anyTO.getETagValue(), req);
}
if (anyTO instanceof AnyObjectTO) {
AnyObjectTO anyObjectTO = MAPPER.readValue(json, AnyObjectTO.class);
AnyObjectUR req = AnyOperations.diff(anyObjectTO, anyTO, false);
return new AnyObjectRestClient().update(anyTO.getETagValue(), req);
}
} catch (final Exception e) {
LOG.error("Could not restore object for {}", anyTO, e);
}
throw SyncopeClientException.build(ClientExceptionType.InvalidAnyObject);
}
private static String getJSONFromAuditEntry(final AuditEntryTO entryBean) throws JsonProcessingException {
final String json;
if (entryBean.getBefore() == null) {
json = MAPPER.readTree(entryBean.getOutput()).get("entity").toPrettyString();
} else {
json = entryBean.getBefore();
}
return json;
}
private static SortParam<String> getSortParam() {
return new SortParam<>("event_date", false);
}
private static AuditElements.Result getQueryableAuditResult() {
return AuditElements.Result.SUCCESS;
}
private static List<String> getQueryableAuditEvents() {
return Arrays.asList("create", "update");
}
@Override
protected AuditHistoryDirectoryPanel.AuditHistoryProvider dataProvider() {
return new AuditHistoryProvider(rows);
}
@Override
protected String paginatorRowsKey() {
return IdRepoConstants.PREF_AUDIT_HISTORY_PAGINATOR_ROWS;
}
@Override
protected List<IColumn<AuditEntryTO, String>> getColumns() {
final List<IColumn<AuditEntryTO, String>> columns = new ArrayList<>();
columns.add(new PropertyColumn<>(
new StringResourceModel("who", this), "who"));
columns.add(new DatePropertyColumn<>(
new StringResourceModel("date", this), null, "date"));
return columns;
}
@Override
protected void resultTableCustomChanges(
final AjaxDataTablePanel.Builder<AuditEntryTO, String> resultTableBuilder) {
resultTableBuilder.setMultiLevelPanel(baseModal, multiLevelPanelRef);
}
@Override
protected ActionsPanel<AuditEntryTO> getActions(final IModel<AuditEntryTO> model) {
final ActionsPanel<AuditEntryTO> panel = super.getActions(model);
panel.add(new ActionLink<AuditEntryTO>() {
private static final long serialVersionUID = -6745431735457245600L;
@Override
public void onClick(final AjaxRequestTarget target, final AuditEntryTO modelObject) {
AuditHistoryDirectoryPanel.this.getTogglePanel().close(target);
viewAuditHistory(modelObject, target);
target.add(modal);
}
}, ActionLink.ActionType.VIEW, IdRepoEntitlement.AUDIT_READ);
final String auditRestoreEntitlement;
switch (this.anyTypeKind) {
case USER:
auditRestoreEntitlement = IdRepoEntitlement.USER_UPDATE;
break;
case GROUP:
auditRestoreEntitlement = IdRepoEntitlement.GROUP_UPDATE;
break;
default:
auditRestoreEntitlement = IdRepoEntitlement.ANYTYPE_UPDATE;
break;
}
panel.add(new ActionLink<AuditEntryTO>() {
private static final long serialVersionUID = -6745431735457245600L;
@Override
public void onClick(final AjaxRequestTarget target, final AuditEntryTO modelObject) {
try {
AuditHistoryDirectoryPanel.this.getTogglePanel().close(target);
ProvisioningResult<? extends AnyTO> result = restore(modelObject, anyTO);
anyTO.setLastChangeDate(new Date(Long.parseLong(result.getEntity().getETagValue())));
target.add(container);
} catch (SyncopeClientException e) {
LOG.error("While restoring {}", anyTypeKind, e);
SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage())
? e.getClass().getName() : e.getMessage());
}
((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
}
}, ActionLink.ActionType.RESTORE, auditRestoreEntitlement);
return panel;
}
@Override
protected Collection<ActionLink.ActionType> getBatches() {
return Collections.emptyList();
}
private void viewAuditHistory(final AuditEntryTO auditEntryBean, final AjaxRequestTarget target) {
List<AuditEntryTO> search = AuditHistoryRestClient.search(anyTO.getKey(),
0,
TOTAL_AUDIT_HISTORY_COMPARISONS,
getSortParam(),
getQueryableAuditEvents(),
getQueryableAuditResult());
multiLevelPanelRef.next(
new StringResourceModel("audit.diff.view", this).getObject(),
new HistoryAuditDetails(modal, auditEntryBean,
getPage().getPageReference(), toAuditEntryTOs(search), anyTO, anyTypeKind), target);
}
private List<AuditEntryTO> toAuditEntryTOs(final List<AuditEntryTO> search) {
return search
.stream()
.map(entry -> {
AuditEntryTO bean = new AuditEntryTO();
bean.setKey(anyTO.getKey());
bean.setBefore(entry.getBefore());
bean.setDate(entry.getDate());
bean.setEvent(entry.getEvent());
bean.getInputs().addAll(entry.getInputs());
bean.setLoggerName(entry.getLoggerName());
bean.setOutput(entry.getOutput());
bean.setResult(entry.getResult());
bean.setSubCategory(entry.getSubCategory());
bean.setThrowable(entry.getThrowable());
bean.setWho(entry.getWho());
return bean;
})
.collect(Collectors.toList());
}
protected class AuditHistoryProvider extends DirectoryDataProvider<AuditEntryTO> {
private static final long serialVersionUID = 415113175628260864L;
AuditHistoryProvider(final int paginatorRows) {
super(paginatorRows);
}
@Override
public Iterator<? extends AuditEntryTO> iterator(final long first, final long count) {
return getAuditEntryBeans(first, count).iterator();
}
@Override
public long size() {
return AuditHistoryRestClient.count(anyTO.getKey(), getQueryableAuditEvents(), getQueryableAuditResult());
}
@Override
public IModel<AuditEntryTO> model(final AuditEntryTO auditEntryBean) {
return new CompoundPropertyModel<>(auditEntryBean);
}
private List<AuditEntryTO> getAuditEntryBeans(final long first, final long count) {
int page = (int) first / paginatorRows;
return AuditHistoryRestClient.search(anyTO.getKey(),
Math.max(page, 0) + 1,
Long.valueOf(count).intValue(),
getSortParam(),
getQueryableAuditEvents(),
getQueryableAuditResult());
}
}
}