/* * Copyright (c) 2010-2013 Evolveum * * 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.evolveum.midpoint.notifications.impl; import com.evolveum.midpoint.model.api.context.ModelContext; import com.evolveum.midpoint.model.api.context.ModelElementContext; import com.evolveum.midpoint.model.api.expr.MidpointFunctions; import com.evolveum.midpoint.notifications.api.NotificationFunctions; import com.evolveum.midpoint.notifications.api.OperationStatus; import com.evolveum.midpoint.notifications.api.events.Event; import com.evolveum.midpoint.notifications.api.events.ModelEvent; import com.evolveum.midpoint.notifications.api.events.ResourceObjectEvent; import com.evolveum.midpoint.notifications.api.events.SimpleObjectRef; import com.evolveum.midpoint.notifications.impl.formatters.TextFormatter; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismPropertyValue; import com.evolveum.midpoint.prism.crypto.EncryptionException; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.DeltaConvertor; import com.evolveum.midpoint.schema.processor.ResourceAttribute; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.schema.util.ShadowUtil; import com.evolveum.midpoint.util.exception.CommonException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import java.util.*; /** * @author mederly */ @Component public class NotificationFunctionsImpl implements NotificationFunctions { private static final Trace LOGGER = TraceManager.getTrace(NotificationFunctionsImpl.class); @Autowired @Qualifier("cacheRepositoryService") private RepositoryService cacheRepositoryService; @Autowired private MidpointFunctions midpointFunctions; @Autowired protected TextFormatter textFormatter; @Autowired private PrismContext prismContext; private static final List<ItemPath> SYNCHRONIZATION_PATHS = Collections.unmodifiableList(Arrays.asList( new ItemPath(ShadowType.F_SYNCHRONIZATION_SITUATION), new ItemPath(ShadowType.F_SYNCHRONIZATION_SITUATION_DESCRIPTION), new ItemPath(ShadowType.F_SYNCHRONIZATION_TIMESTAMP), new ItemPath(ShadowType.F_FULL_SYNCHRONIZATION_TIMESTAMP))); private static final List<ItemPath> AUXILIARY_PATHS = Collections.unmodifiableList(Arrays.asList( new ItemPath(ShadowType.F_METADATA), new ItemPath(ShadowType.F_ACTIVATION, ActivationType.F_VALIDITY_STATUS), // works for user activation as well new ItemPath(ShadowType.F_ACTIVATION, ActivationType.F_VALIDITY_CHANGE_TIMESTAMP), new ItemPath(ShadowType.F_ACTIVATION, ActivationType.F_EFFECTIVE_STATUS), new ItemPath(ShadowType.F_ACTIVATION, ActivationType.F_DISABLE_TIMESTAMP), new ItemPath(ShadowType.F_ACTIVATION, ActivationType.F_ARCHIVE_TIMESTAMP), new ItemPath(ShadowType.F_ACTIVATION, ActivationType.F_ENABLE_TIMESTAMP), new ItemPath(ShadowType.F_ITERATION), new ItemPath(ShadowType.F_ITERATION_TOKEN), new ItemPath(UserType.F_LINK_REF), new ItemPath(ShadowType.F_TRIGGER)) ); // beware, may return null if there's any problem getting sysconfig (e.g. during initial import) public static SystemConfigurationType getSystemConfiguration(RepositoryService repositoryService, OperationResult result) { try { return repositoryService.getObject(SystemConfigurationType.class, SystemObjectsType.SYSTEM_CONFIGURATION.value(), null, result).asObjectable(); } catch (ObjectNotFoundException|SchemaException e) { LoggingUtils.logException(LOGGER, "Notification(s) couldn't be processed, because the system configuration couldn't be retrieved", e); return null; } } public SystemConfigurationType getSystemConfiguration(OperationResult result) { try { return cacheRepositoryService.getObject(SystemConfigurationType.class, SystemObjectsType.SYSTEM_CONFIGURATION.value(), null, result).asObjectable(); } catch (ObjectNotFoundException|SchemaException e) { LoggingUtils.logException(LOGGER, "Notification(s) couldn't be processed, because the system configuration couldn't be retrieved", e); return null; } } public static SecurityPolicyType getSecurityPolicyConfiguration(ObjectReferenceType securityPolicyRef, RepositoryService repositoryService, OperationResult result) { try { if (securityPolicyRef == null) { return null; } return repositoryService.getObject(SecurityPolicyType.class, securityPolicyRef.getOid(), null, result).asObjectable(); } catch (ObjectNotFoundException|SchemaException e) { LoggingUtils.logException(LOGGER, "Notification(s) couldn't be processed, because the security policy configuration couldn't be retrieved", e); return null; } } public static String getResourceNameFromRepo(RepositoryService repositoryService, String oid, OperationResult result) { try { PrismObject<ResourceType> resource = repositoryService.getObject(ResourceType.class, oid, null, result); return PolyString.getOrig(resource.asObjectable().getName()); } catch (ObjectNotFoundException e) { LoggingUtils.logException(LOGGER, "Couldn't get resource", e); return null; } catch (SchemaException e) { LoggingUtils.logException(LOGGER, "Couldn't get resource", e); return null; } } public String getObjectName(String oid, OperationResult result) { try { PrismObject<? extends ObjectType> object = cacheRepositoryService.getObject(ObjectType.class, oid, null, result); return PolyString.getOrig(object.asObjectable().getName()); } catch (CommonException|RuntimeException e) { LoggingUtils.logUnexpectedException(LOGGER, "Couldn't get resource", e); return null; } } public ObjectType getObjectType(SimpleObjectRef simpleObjectRef, boolean allowNotFound, OperationResult result) { if (simpleObjectRef == null) { return null; } if (simpleObjectRef.getObjectType() != null) { return simpleObjectRef.getObjectType(); } if (simpleObjectRef.getOid() == null) { return null; } ObjectType objectType = getObjectFromRepo(simpleObjectRef.getOid(), allowNotFound, result); simpleObjectRef.setObjectType(objectType); return objectType; } public ObjectType getObjectType(ObjectReferenceType ref, boolean allowNotFound, OperationResult result) { if (ref == null) { return null; } if (ref.asReferenceValue().getObject() != null) { return (ObjectType) ref.asReferenceValue().getObject().asObjectable(); } if (ref.getOid() == null) { return null; } return getObjectFromRepo(ref.getOid(), allowNotFound, result); } @Nullable private ObjectType getObjectFromRepo(String oid, boolean allowNotFound, OperationResult result) { ObjectType objectType; try { objectType = cacheRepositoryService.getObject(ObjectType.class, oid, null, result).asObjectable(); } catch (ObjectNotFoundException e) { // todo correct error handling if (allowNotFound) { return null; } else { throw new SystemException(e); } } catch (SchemaException e) { throw new SystemException(e); } return objectType; } public static boolean isAmongHiddenPaths(ItemPath path, List<ItemPath> hiddenPaths) { if (hiddenPaths == null) { return false; } for (ItemPath hiddenPath : hiddenPaths) { if (hiddenPath.isSubPathOrEquivalent(path)) { return true; } } return false; } @Override public String getShadowName(PrismObject<? extends ShadowType> shadow) { if (shadow == null) { return null; } else if (shadow.asObjectable().getName() != null) { return shadow.asObjectable().getName().getOrig(); } else { Collection<ResourceAttribute<?>> secondaryIdentifiers = ShadowUtil.getSecondaryIdentifiers(shadow); LOGGER.trace("secondary identifiers: {}", secondaryIdentifiers); // first phase = looking for "name" identifier for (ResourceAttribute ra : secondaryIdentifiers) { if (ra.getElementName() != null && ra.getElementName().getLocalPart().contains("name")) { LOGGER.trace("Considering {} as a name", ra); return String.valueOf(ra.getAnyRealValue()); } } // second phase = returning any value ;) if (!secondaryIdentifiers.isEmpty()) { return String.valueOf(secondaryIdentifiers.iterator().next().getAnyRealValue()); } else { return null; } } } // TODO move to some other class? public void addRequesterAndChannelInformation(StringBuilder body, Event event, OperationResult result) { if (event.getRequester() != null) { body.append("Requester: "); try { ObjectType requester = event.getRequester().resolveObjectType(result, false); if (requester instanceof UserType) { UserType requesterUser = (UserType) requester; body.append(requesterUser.getFullName()).append(" (").append(requester.getName()).append(")"); } else { body.append(ObjectTypeUtil.toShortString(requester)); } } catch (RuntimeException e) { body.append("couldn't be determined: ").append(e.getMessage()); LoggingUtils.logUnexpectedException(LOGGER, "Couldn't determine requester for a notification", e); } body.append("\n"); } body.append("Channel: ").append(event.getChannel()).append("\n\n"); } @Override public String getPlaintextPasswordFromDelta(ObjectDelta delta) { try { return midpointFunctions.getPlaintextAccountPasswordFromDelta(delta); } catch (EncryptionException e) { LoggingUtils.logException(LOGGER, "Couldn't decrypt password from shadow delta: {}", e, delta.debugDump()); return null; } } @Override public List<ItemPath> getSynchronizationPaths() { return SYNCHRONIZATION_PATHS; } @Override public List<ItemPath> getAuxiliaryPaths() { return AUXILIARY_PATHS; } public String getContentAsFormattedList(Event event, boolean showSynchronizationItems, boolean showAuxiliaryAttributes) { List<ItemPath> hiddenPaths = new ArrayList<>(); if (!showSynchronizationItems) { hiddenPaths.addAll(SYNCHRONIZATION_PATHS); } if (!showAuxiliaryAttributes) { hiddenPaths.addAll(AUXILIARY_PATHS); } if (event instanceof ResourceObjectEvent) { final ResourceObjectEvent resourceObjectEvent = (ResourceObjectEvent) event; final ObjectDelta<ShadowType> shadowDelta = resourceObjectEvent.getShadowDelta(); if (shadowDelta == null) { return ""; } if (shadowDelta.isAdd()) { return getResourceObjectAttributesAsFormattedList(shadowDelta.getObjectToAdd().asObjectable(), hiddenPaths, showAuxiliaryAttributes); } else if (shadowDelta.isModify()) { return getResourceObjectModifiedAttributesAsFormattedList(resourceObjectEvent, shadowDelta, hiddenPaths, showAuxiliaryAttributes); } else { return ""; } } else if (event instanceof ModelEvent) { final ModelEvent modelEvent = (ModelEvent) event; ModelContext<FocusType> modelContext = (ModelContext) modelEvent.getModelContext(); ModelElementContext<FocusType> focusContext = modelContext.getFocusContext(); ObjectDelta<? extends FocusType> summarizedDelta; try { summarizedDelta = modelEvent.getSummarizedFocusDeltas(); } catch (SchemaException e) { LoggingUtils.logUnexpectedException(LOGGER, "Unable to determine the focus change; focus context = {}", e, focusContext.debugDump()); return("(unable to determine the change because of schema exception: " + e.getMessage() + ")\n"); } if (summarizedDelta.isAdd()) { return textFormatter.formatObject(summarizedDelta.getObjectToAdd(), hiddenPaths, showAuxiliaryAttributes); } else if (summarizedDelta.isModify()) { return textFormatter.formatObjectModificationDelta(summarizedDelta, hiddenPaths, showAuxiliaryAttributes, focusContext.getObjectOld(), focusContext.getObjectNew()); } else { return ""; } } else { return ""; } } private String getResourceObjectAttributesAsFormattedList(ShadowType shadowType, List<ItemPath> hiddenAttributes, boolean showAuxiliaryAttributes) { return textFormatter.formatAccountAttributes(shadowType, hiddenAttributes, false); } private String getResourceObjectModifiedAttributesAsFormattedList(ResourceObjectEvent event, ObjectDelta<ShadowType> shadowDelta, List<ItemPath> hiddenPaths, boolean showAuxiliaryAttributes) { StringBuilder rv = new StringBuilder(); if (event.getOperationStatus() != OperationStatus.IN_PROGRESS) { // todo we do not have objectOld + objectNew, only the current status // it is used to explain modified containers with identifiers -- however, currently I don't know of use of such containers in shadows, which would be visible in notifications rv.append(textFormatter.formatObjectModificationDelta(shadowDelta, hiddenPaths, showAuxiliaryAttributes, event.getAccountOperationDescription().getCurrentShadow(), null)); } else { // special case - here the attributes are 'result', 'failedOperationType', 'objectChange', 'attemptNumber' // we have to unwrap attributes that are to be modified from the objectChange item Collection<PrismPropertyValue<ObjectDeltaType>> changes = null; if (shadowDelta.getModifications() != null) { for (ItemDelta itemDelta : shadowDelta.getModifications()) { if (itemDelta.getPath().equivalent(new ItemPath(ShadowType.F_OBJECT_CHANGE))) { changes = itemDelta.getValuesToAdd() != null && !itemDelta.getValuesToAdd().isEmpty() ? itemDelta.getValuesToAdd() : itemDelta.getValuesToReplace(); } } } if (changes != null && !changes.isEmpty()) { try { List<ObjectDelta<ShadowType>> deltas = new ArrayList<>(changes.size()); for (PrismPropertyValue<ObjectDeltaType> change : changes) { deltas.add((ObjectDelta) DeltaConvertor.createObjectDelta(change.getValue(), prismContext)); } ObjectDelta<ShadowType> summarizedDelta = ObjectDelta.summarize(deltas); rv.append(textFormatter.formatObjectModificationDelta(summarizedDelta, hiddenPaths, showAuxiliaryAttributes, event.getAccountOperationDescription().getCurrentShadow(), null)); } catch (SchemaException e) { LoggingUtils.logUnexpectedException(LOGGER, "Unable to determine the shadow change; operation = {}", e, event.getAccountOperationDescription().debugDump()); rv.append("(unable to determine the change because of schema exception: ").append(e.getMessage()).append(")\n"); } } else { rv.append("(unable to determine the change)\n"); } } return rv.toString(); } }