/* * Copyright (c) 2010-2016 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.model.impl.controller; import com.evolveum.midpoint.audit.api.AuditEventRecord; import com.evolveum.midpoint.audit.api.AuditEventStage; import com.evolveum.midpoint.audit.api.AuditEventType; import com.evolveum.midpoint.audit.api.AuditService; import com.evolveum.midpoint.certification.api.CertificationManager; import com.evolveum.midpoint.model.api.*; import com.evolveum.midpoint.model.api.hooks.HookRegistry; import com.evolveum.midpoint.model.api.hooks.ReadHook; import com.evolveum.midpoint.model.common.SystemObjectCache; import com.evolveum.midpoint.model.impl.ModelObjectResolver; import com.evolveum.midpoint.model.impl.importer.ImportAccountsFromResourceTaskHandler; import com.evolveum.midpoint.model.impl.importer.ObjectImporter; import com.evolveum.midpoint.model.impl.lens.*; import com.evolveum.midpoint.model.impl.lens.projector.Projector; import com.evolveum.midpoint.model.impl.scripting.ExecutionContext; import com.evolveum.midpoint.model.impl.scripting.ScriptingExpressionEvaluator; import com.evolveum.midpoint.model.impl.util.Utils; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.Visitor; import com.evolveum.midpoint.prism.crypto.Protector; import com.evolveum.midpoint.prism.delta.ChangeType; import com.evolveum.midpoint.prism.delta.DiffUtil; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.path.ItemPathSegment; import com.evolveum.midpoint.prism.path.NameItemPathSegment; import com.evolveum.midpoint.prism.path.ParentPathSegment; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.query.*; import com.evolveum.midpoint.prism.query.builder.QueryBuilder; import com.evolveum.midpoint.provisioning.api.ProvisioningOperationOptions; import com.evolveum.midpoint.provisioning.api.ProvisioningService; import com.evolveum.midpoint.repo.api.RepoAddOptions; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.repo.cache.RepositoryCache; import com.evolveum.midpoint.schema.*; import com.evolveum.midpoint.schema.constants.ObjectTypes; import com.evolveum.midpoint.schema.internals.InternalsConfig; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultRunner; import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.schema.util.ObjectQueryUtil; import com.evolveum.midpoint.schema.util.ShadowUtil; import com.evolveum.midpoint.security.api.AuthorizationConstants; import com.evolveum.midpoint.security.api.SecurityEnforcer; import com.evolveum.midpoint.security.api.UserProfileService; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.task.api.TaskManager; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.*; 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.wf.api.WorkflowManager; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.CompareResultType; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ImportOptionsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ScriptingExpressionType; import com.evolveum.prism.xml.ns._public.types_3.EvaluationTimeType; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.Validate; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import javax.xml.namespace.QName; import java.io.*; import java.util.*; /** * This used to be an interface, but it was switched to class for simplicity. I * don't expect that the implementation of the controller will be ever replaced. * In extreme case the whole Model will be replaced by a different * implementation, but not just the controller. * <p/> * However, the common way to extend the functionality will be the use of hooks * that are implemented here. * <p/> * Great deal of code is copied from the old ModelControllerImpl. * * @author lazyman * @author Radovan Semancik * * Note: don't autowire this bean by implementing class (ModelController), as it is proxied by Spring AOP. * Use its interfaces instead. */ @Component public class ModelController implements ModelService, TaskService, WorkflowService, ScriptingService, AccessCertificationService { // Constants for OperationResult public static final String CLASS_NAME_WITH_DOT = ModelController.class.getName() + "."; public static final String ADD_OBJECT_WITH_EXCLUSION = CLASS_NAME_WITH_DOT + "addObjectWithExclusion"; public static final String MODIFY_OBJECT_WITH_EXCLUSION = CLASS_NAME_WITH_DOT + "modifyObjectWithExclusion"; public static final String CHANGE_ACCOUNT = CLASS_NAME_WITH_DOT + "changeAccount"; public static final String GET_SYSTEM_CONFIGURATION = CLASS_NAME_WITH_DOT + "getSystemConfiguration"; public static final String RESOLVE_USER_ATTRIBUTES = CLASS_NAME_WITH_DOT + "resolveUserAttributes"; public static final String RESOLVE_ACCOUNT_ATTRIBUTES = CLASS_NAME_WITH_DOT + "resolveAccountAttributes"; public static final String CREATE_ACCOUNT = CLASS_NAME_WITH_DOT + "createAccount"; public static final String UPDATE_ACCOUNT = CLASS_NAME_WITH_DOT + "updateAccount"; public static final String PROCESS_USER_TEMPLATE = CLASS_NAME_WITH_DOT + "processUserTemplate"; private static final Trace LOGGER = TraceManager.getTrace(ModelController.class); @Autowired(required = true) private Clockwork clockwork; @Autowired(required = true) private PrismContext prismContext; @Autowired(required = true) private ProvisioningService provisioning; @Autowired(required = true) private ModelObjectResolver objectResolver; @Autowired(required = true) @Qualifier("cacheRepositoryService") private transient RepositoryService cacheRepositoryService; @Autowired(required = true) private transient ImportAccountsFromResourceTaskHandler importAccountsFromResourceTaskHandler; @Autowired(required = true) private transient ObjectImporter objectImporter; @Autowired(required = false) private HookRegistry hookRegistry; @Autowired(required = true) private TaskManager taskManager; @Autowired(required = false) // not required in all circumstances private WorkflowManager workflowManager; @Autowired(required = false) // not required in all circumstances private CertificationManager certificationManager; @Autowired(required = true) private ScriptingExpressionEvaluator scriptingExpressionEvaluator; @Autowired(required = true) private ChangeExecutor changeExecutor; @Autowired(required = true) private AuditService auditService; @Autowired(required = true) private SecurityEnforcer securityEnforcer; @Autowired(required = true) private UserProfileService userProfileService; @Autowired(required = true) private Projector projector; @Autowired(required = true) private Protector protector; @Autowired(required = true) private ModelDiagController modelDiagController; @Autowired(required = true) private ContextFactory contextFactory; @Autowired(required = true) private SchemaTransformer schemaTransformer; @Autowired(required = true) private ObjectMerger objectMerger; @Autowired(required = true) private SystemObjectCache systemObjectCache; public ModelObjectResolver getObjectResolver() { return objectResolver; } private WorkflowManager getWorkflowManagerChecked() { if (workflowManager == null) { throw new SystemException("Workflow manager not present"); } return workflowManager; } private CertificationManager getCertificationManagerChecked() { if (certificationManager == null) { throw new SystemException("Certification manager not present"); } return certificationManager; } @Override public <T extends ObjectType> PrismObject<T> getObject(Class<T> clazz, String oid, Collection<SelectorOptions<GetOperationOptions>> options, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { Validate.notEmpty(oid, "Object oid must not be null or empty."); Validate.notNull(parentResult, "Operation result must not be null."); Validate.notNull(clazz, "Object class must not be null."); RepositoryCache.enter(); PrismObject<T> object = null; OperationResult result = parentResult.createMinorSubresult(GET_OBJECT); result.addParam("oid", oid); result.addCollectionOfSerializablesAsParam("options", options); result.addParam("class", clazz); GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options); try { if (GetOperationOptions.isRaw(rootOptions)) { // MID-2218 QNameUtil.setTemporarilyTolerateUndeclaredPrefixes(true); } ObjectReferenceType ref = new ObjectReferenceType(); ref.setOid(oid); ref.setType(ObjectTypes.getObjectType(clazz).getTypeQName()); Utils.clearRequestee(task); // Special-purpose code to hunt down read-write resource fetch from GUI. // Normally the code is not active. It is too brutal. Just for MID-3424. // if (ResourceType.class == clazz && !GetOperationOptions.isRaw(rootOptions) && !GetOperationOptions.isReadOnly(rootOptions)) { // LOGGER.info("READWRITE resource get: {} {}:\n{}", oid, options, // LoggingUtils.dumpStackTrace()); // } object = (PrismObject<T>) objectResolver.getObject(clazz, oid, options, task, result).asPrismObject(); object = object.cloneIfImmutable(); schemaTransformer.applySchemasAndSecurity(object, rootOptions, null, task, result); resolve(object, options, task, result); } catch (SchemaException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (ObjectNotFoundException e) { if (GetOperationOptions.isAllowNotFound(rootOptions)){ result.getLastSubresult().setStatus(OperationResultStatus.HANDLED_ERROR); } else { ModelUtils.recordFatalError(result, e); } throw e; } catch (CommunicationException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (ConfigurationException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (SecurityViolationException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (RuntimeException e) { ModelUtils.recordFatalError(result, e); throw e; } finally { QNameUtil.setTemporarilyTolerateUndeclaredPrefixes(false); RepositoryCache.exit(); } result.cleanupResult(); return object; } protected void resolve(PrismObject<?> object, Collection<SelectorOptions<GetOperationOptions>> options, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, SecurityViolationException, ConfigurationException { if (object == null) { return; } resolve(object.asObjectable(), options, task, result); } protected void resolve(Containerable containerable, Collection<SelectorOptions<GetOperationOptions>> options, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, SecurityViolationException, ConfigurationException { if (containerable == null || options == null) { return; } for (SelectorOptions<GetOperationOptions> option: options) { try { resolve(containerable, option, task, result); } catch (ObjectNotFoundException ex) { result.recordWarning(ex.getMessage(), ex); } } } private void resolve(Containerable object, SelectorOptions<GetOperationOptions> option, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, SecurityViolationException, ConfigurationException { if (!GetOperationOptions.isResolve(option.getOptions())) { return; } ObjectSelector selector = option.getSelector(); if (selector == null) { return; } ItemPath path = selector.getPath(); ItemPath.checkNoSpecialSymbolsExceptParent(path); resolve(object, path, option, task, result); } // TODO clean this mess private <O extends ObjectType> void resolve(Containerable containerable, ItemPath path, SelectorOptions<GetOperationOptions> option, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, SecurityViolationException, ConfigurationException { if (path == null || path.isEmpty()) { return; } ItemPathSegment first = path.first(); ItemPath rest = path.rest(); PrismContainerValue<?> containerValue = containerable.asPrismContainerValue(); if (first instanceof NameItemPathSegment) { QName refName = ItemPath.getName(first); PrismReference reference = containerValue.findReferenceByCompositeObjectElementName(refName); if (reference == null) { reference = containerValue.findReference(refName); // alternatively look up by reference name (e.g. linkRef) } if (reference != null) { for (PrismReferenceValue refVal : reference.getValues()) { PrismObject<O> refObject = refVal.getObject(); if (refObject == null) { refObject = objectResolver.resolve(refVal, containerable.toString(), option.getOptions(), task, result); refObject = refObject.cloneIfImmutable(); schemaTransformer.applySchemasAndSecurity(refObject, option.getOptions(), null, task, result); refVal.setObject(refObject); } if (!rest.isEmpty()) { resolve(refObject.asObjectable(), rest, option, task, result); } } return; } } if (rest.isEmpty()) { return; } if (first instanceof ParentPathSegment) { PrismContainerValue<?> parent = containerValue.getParentContainerValue(); if (parent != null) { resolve(parent.asContainerable(), rest, option, task, result); } } else { QName nextName = ItemPath.getName(first); PrismContainer<?> nextContainer = containerValue.findContainer(nextName); if (nextContainer != null) { for (PrismContainerValue<?> pcv : nextContainer.getValues()) { resolve(pcv.asContainerable(), rest, option, task, result); } } } } @Override public Collection<ObjectDeltaOperation<? extends ObjectType>> executeChanges(final Collection<ObjectDelta<? extends ObjectType>> deltas, ModelExecuteOptions options, Task task, OperationResult parentResult) throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, PolicyViolationException, SecurityViolationException { return executeChanges(deltas, options, task, null, parentResult); } /* (non-Javadoc) * @see com.evolveum.midpoint.model.api.ModelService#executeChanges(java.util.Collection, com.evolveum.midpoint.task.api.Task, com.evolveum.midpoint.schema.result.OperationResult) */ @Override public Collection<ObjectDeltaOperation<? extends ObjectType>> executeChanges(final Collection<ObjectDelta<? extends ObjectType>> deltas, ModelExecuteOptions options, Task task, Collection<ProgressListener> statusListeners, OperationResult parentResult) throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, PolicyViolationException, SecurityViolationException { Collection<ObjectDeltaOperation<? extends ObjectType>> executedDeltas = new ArrayList<>(); OperationResult result = parentResult.createSubresult(EXECUTE_CHANGES); result.addParam(OperationResult.PARAM_OPTIONS, options); // Search filters treatment: if reevaluation is requested, we have to deal with three cases: // 1) for ADD operation: filters contained in object-to-be-added -> these are treated here // 2) for MODIFY operation: filters contained in existing object (not touched by deltas) -> these are treated after the modify operation // 3) for MODIFY operation: filters contained in deltas -> these have to be treated here, because if OID is missing from such a delta, the change would be rejected by the repository if (ModelExecuteOptions.isReevaluateSearchFilters(options)) { for (ObjectDelta<? extends ObjectType> delta : deltas) { Utils.resolveReferences(delta, cacheRepositoryService, false, true, EvaluationTimeType.IMPORT, true, prismContext, result); } } else if (ModelExecuteOptions.isIsImport(options)) { // if plain import is requested, we simply evaluate filters in ADD operation (and we do not force reevaluation if OID is already set) for (ObjectDelta<? extends ObjectType> delta : deltas) { if (delta.isAdd()) { Utils.resolveReferences(delta.getObjectToAdd(), cacheRepositoryService, false, false, EvaluationTimeType.IMPORT, true, prismContext, result); } } } // Make sure everything is encrypted as needed before logging anything. // But before that we need to make sure that we have proper definition, otherwise we // might miss some encryptable data in dynamic schemas applyDefinitions(deltas, options, result); Utils.encrypt(deltas, protector, options, result); if (LOGGER.isTraceEnabled()) { LOGGER.trace("MODEL.executeChanges(\n deltas:\n{}\n options:{}", DebugUtil.debugDump(deltas, 2), options); } if (InternalsConfig.consistencyChecks) { OperationResultRunner.run(result, () -> { for (ObjectDelta<? extends ObjectType> delta : deltas) { delta.checkConsistence(); } }); } RepositoryCache.enter(); try { if (ModelExecuteOptions.isRaw(options)) { // Go directly to repository AuditEventRecord auditRecord = new AuditEventRecord(AuditEventType.EXECUTE_CHANGES_RAW, AuditEventStage.REQUEST); auditRecord.addDeltas(ObjectDeltaOperation.cloneDeltaCollection(deltas)); auditRecord.setTarget(Utils.determineAuditTarget(deltas)); // we don't know auxiliary information (resource, objectName) at this moment -- so we do nothing auditService.audit(auditRecord, task); try { for (ObjectDelta<? extends ObjectType> delta : deltas) { OperationResult result1 = result.createSubresult(EXECUTE_CHANGE); // MID-2486 if (delta.getObjectTypeClass() == ShadowType.class || delta.getObjectTypeClass() == ResourceType.class) { try { provisioning.applyDefinition(delta, result1); } catch (SchemaException | ObjectNotFoundException | CommunicationException | ConfigurationException | RuntimeException e) { // we can tolerate this - if there's a real problem with definition, repo call below will fail LoggingUtils.logExceptionAsWarning(LOGGER, "Couldn't apply definition on shadow/resource raw-mode delta {} -- continuing the operation.", e, delta); result1.muteLastSubresultError(); } } final boolean preAuthorized = ModelExecuteOptions.isPreAuthorized(options); PrismObject objectToDetermineDetailsForAudit = null; try { if (delta.isAdd()) { RepoAddOptions repoOptions = new RepoAddOptions(); if (ModelExecuteOptions.isNoCrypt(options)) { repoOptions.setAllowUnencryptedValues(true); } if (ModelExecuteOptions.isOverwrite(options)) { repoOptions.setOverwrite(true); } PrismObject<? extends ObjectType> objectToAdd = delta.getObjectToAdd(); if (!preAuthorized) { securityEnforcer.authorize(ModelAuthorizationAction.ADD.getUrl(), null, objectToAdd, null, null, null, result1); } String oid; try { oid = cacheRepositoryService.addObject(objectToAdd, repoOptions, result1); task.recordObjectActionExecuted(objectToAdd, null, oid, ChangeType.ADD, task.getChannel(), null); } catch (Throwable t) { task.recordObjectActionExecuted(objectToAdd, null, null, ChangeType.ADD, task.getChannel(), t); throw t; } delta.setOid(oid); objectToDetermineDetailsForAudit = objectToAdd; } else if (delta.isDelete()) { QNameUtil.setTemporarilyTolerateUndeclaredPrefixes(true); // MID-2218 try { PrismObject<? extends ObjectType> existingObject = null; try { existingObject = cacheRepositoryService.getObject(delta.getObjectTypeClass(), delta.getOid(), null, result1); objectToDetermineDetailsForAudit = existingObject; } catch (Throwable t) { if (!securityEnforcer.isAuthorized(AuthorizationConstants.AUTZ_ALL_URL, null, null, null, null, null)) { throw t; } else { // in case of administrator's request we continue - in order to allow deleting malformed (unreadable) objects } } if (!preAuthorized) { securityEnforcer.authorize(ModelAuthorizationAction.DELETE.getUrl(), null, existingObject, null, null, null, result1); } try { if (ObjectTypes.isClassManagedByProvisioning(delta.getObjectTypeClass())) { Utils.clearRequestee(task); provisioning.deleteObject(delta.getObjectTypeClass(), delta.getOid(), ProvisioningOperationOptions.createRaw(), null, task, result1); } else { cacheRepositoryService.deleteObject(delta.getObjectTypeClass(), delta.getOid(), result1); } task.recordObjectActionExecuted(objectToDetermineDetailsForAudit, delta.getObjectTypeClass(), delta.getOid(), ChangeType.DELETE, task.getChannel(), null); } catch (Throwable t) { task.recordObjectActionExecuted(objectToDetermineDetailsForAudit, delta.getObjectTypeClass(), delta.getOid(), ChangeType.DELETE, task.getChannel(), t); throw t; } } finally { QNameUtil.setTemporarilyTolerateUndeclaredPrefixes(false); } } else if (delta.isModify()) { QNameUtil.setTemporarilyTolerateUndeclaredPrefixes(true); // MID-2218 try { PrismObject existingObject = cacheRepositoryService.getObject(delta.getObjectTypeClass(), delta.getOid(), null, result1); objectToDetermineDetailsForAudit = existingObject; if (!preAuthorized) { securityEnforcer.authorize(ModelAuthorizationAction.MODIFY.getUrl(), null, existingObject, delta, null, null, result1); } try { cacheRepositoryService.modifyObject(delta.getObjectTypeClass(), delta.getOid(), delta.getModifications(), result1); task.recordObjectActionExecuted(existingObject, ChangeType.MODIFY, null); } catch (Throwable t) { task.recordObjectActionExecuted(existingObject, ChangeType.MODIFY, t); throw t; } } finally { QNameUtil.setTemporarilyTolerateUndeclaredPrefixes(false); } if (ModelExecuteOptions.isReevaluateSearchFilters(options)) { // treat filters that already exist in the object (case #2 above) reevaluateSearchFilters(delta.getObjectTypeClass(), delta.getOid(), task, result1); } } else { throw new IllegalArgumentException("Wrong delta type " + delta.getChangeType() + " in " + delta); } } catch (ObjectAlreadyExistsException | SchemaException | ObjectNotFoundException | ConfigurationException | CommunicationException | SecurityViolationException | RuntimeException e) { ModelUtils.recordFatalError(result1, e); throw e; } finally { // to have a record with the failed delta as well result1.computeStatus(); ObjectDeltaOperation<? extends ObjectType> odoToAudit = new ObjectDeltaOperation<>(delta, result1); if (objectToDetermineDetailsForAudit != null) { odoToAudit.setObjectName(objectToDetermineDetailsForAudit.getName()); if (objectToDetermineDetailsForAudit.asObjectable() instanceof ShadowType) { ShadowType shadow = (ShadowType) objectToDetermineDetailsForAudit.asObjectable(); odoToAudit.setResourceOid(ShadowUtil.getResourceOid(shadow)); odoToAudit.setResourceName(ShadowUtil.getResourceName(shadow)); } } executedDeltas.add(odoToAudit); } } } finally { cleanupOperationResult(result); auditRecord.setTimestamp(System.currentTimeMillis()); auditRecord.setOutcome(result.getStatus()); auditRecord.setEventStage(AuditEventStage.EXECUTION); auditRecord.getDeltas().clear(); auditRecord.getDeltas().addAll(executedDeltas); auditService.audit(auditRecord, task); task.markObjectActionExecutedBoundary(); } } else { try { LensContext<? extends ObjectType> context = contextFactory.createContext(deltas, options, task, result); if (ModelExecuteOptions.isReevaluateSearchFilters(options)) { String m = "ReevaluateSearchFilters option is not fully supported for non-raw operations yet. Filters already present in the object will not be touched."; LOGGER.warn("{} Context = {}", m, context.debugDump()); result.createSubresult(CLASS_NAME_WITH_DOT+"reevaluateSearchFilters").recordWarning(m); } context.setProgressListeners(statusListeners); // Note: Request authorization happens inside clockwork clockwork.run(context, task, result); // prepare return value if (context.getFocusContext() != null) { executedDeltas.addAll(context.getFocusContext().getExecutedDeltas()); } for (LensProjectionContext projectionContext : context.getProjectionContexts()) { executedDeltas.addAll(projectionContext.getExecutedDeltas()); } if (context.hasExplosiveProjection()) { PrismObject<? extends ObjectType> focus = context.getFocusContext().getObjectAny(); LOGGER.debug("Recomputing {} because there was explosive projection", focus); LensContext<? extends ObjectType> recomputeContext = contextFactory.createRecomputeContext(focus, options, task, result); recomputeContext.setDoReconciliationForAllProjections(true); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Recomputing {}, context:\n{}", focus, recomputeContext.debugDump()); } clockwork.run(recomputeContext, task, result); } cleanupOperationResult(result); } catch (ObjectAlreadyExistsException|ObjectNotFoundException|SchemaException|ExpressionEvaluationException| CommunicationException|ConfigurationException|PolicyViolationException|SecurityViolationException|RuntimeException e) { ModelUtils.recordFatalError(result, e); throw e; } finally { task.markObjectActionExecutedBoundary(); } } invalidateCaches(executedDeltas); } catch (RuntimeException e) { // just for sure (TODO split this method into two: raw and non-raw case) ModelUtils.recordFatalError(result, e); throw e; } finally { RepositoryCache.exit(); } return executedDeltas; } private void invalidateCaches(Collection<ObjectDeltaOperation<? extends ObjectType>> executedDeltas) { if (executedDeltas == null) { return; } for (ObjectDeltaOperation<? extends ObjectType> executedDelta: executedDeltas) { ObjectDelta<? extends ObjectType> objectDelta = executedDelta.getObjectDelta(); if (objectDelta != null) { if (objectDelta.getObjectTypeClass() == SystemConfigurationType.class) { systemObjectCache.invalidateCaches(); } } } } protected void cleanupOperationResult(OperationResult result) { // Clockwork.run sets "in-progress" flag just at the root level // and result.computeStatus() would erase it. // So we deal with it in a special way, in order to preserve this information for the user. if (result.isInProgress()) { result.computeStatus(); if (result.isSuccess()) { result.recordInProgress(); } } else { result.computeStatus(); } result.cleanupResult(); } private <T extends ObjectType> void reevaluateSearchFilters(Class<T> objectTypeClass, String oid, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException { OperationResult result = parentResult.createSubresult(CLASS_NAME_WITH_DOT+"reevaluateSearchFilters"); try { PrismObject<T> storedObject = cacheRepositoryService.getObject(objectTypeClass, oid, null, result); PrismObject<T> updatedObject = storedObject.clone(); Utils.resolveReferences(updatedObject, cacheRepositoryService, false, true, EvaluationTimeType.IMPORT, true, prismContext, result); ObjectDelta<T> delta = storedObject.diff(updatedObject); if (LOGGER.isTraceEnabled()) { LOGGER.trace("reevaluateSearchFilters found delta: {}", delta.debugDump()); } if (!delta.isEmpty()) { try { cacheRepositoryService.modifyObject(objectTypeClass, oid, delta.getModifications(), result); task.recordObjectActionExecuted(updatedObject, ChangeType.MODIFY, null); } catch (Throwable t) { task.recordObjectActionExecuted(updatedObject, ChangeType.MODIFY, t); throw t; } } result.recordSuccess(); } catch (SchemaException|ObjectNotFoundException|ObjectAlreadyExistsException|RuntimeException e) { result.recordFatalError("Couldn't reevaluate search filters: "+e.getMessage(), e); throw e; } } @Override public <F extends ObjectType> void recompute(Class<F> type, String oid, Task task, OperationResult parentResult) throws SchemaException, PolicyViolationException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException { ModelExecuteOptions options = ModelExecuteOptions.createReconcile(); recompute(type, oid, options, task, parentResult); } @Override public <F extends ObjectType> void recompute(Class<F> type, String oid, ModelExecuteOptions options, Task task, OperationResult parentResult) throws SchemaException, PolicyViolationException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException { OperationResult result = parentResult.createMinorSubresult(RECOMPUTE); result.addParams(new String[] { "oid", "type" }, oid, type); RepositoryCache.enter(); try { Utils.clearRequestee(task); PrismObject<F> focus = objectResolver.getObject(type, oid, null, task, result).asPrismContainer(); LOGGER.debug("Recomputing {}", focus); LensContext<F> lensContext = contextFactory.createRecomputeContext(focus, options, task, result); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Recomputing {}, context:\n{}", focus, lensContext.debugDump()); } clockwork.run(lensContext, task, result); result.computeStatus(); LOGGER.trace("Recomputing of {}: {}", focus, result.getStatus()); result.cleanupResult(); } catch (ExpressionEvaluationException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (SchemaException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (PolicyViolationException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (ObjectNotFoundException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (ObjectAlreadyExistsException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (CommunicationException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (ConfigurationException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (SecurityViolationException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (RuntimeException e) { ModelUtils.recordFatalError(result, e); throw e; } finally { RepositoryCache.exit(); } } private void applyDefinitions(Collection<ObjectDelta<? extends ObjectType>> deltas, ModelExecuteOptions options, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException { for(ObjectDelta<? extends ObjectType> delta: deltas) { Class<? extends ObjectType> type = delta.getObjectTypeClass(); if (delta.hasCompleteDefinition()) { continue; } if (type == ResourceType.class || ShadowType.class.isAssignableFrom(type)) { try { provisioning.applyDefinition(delta, result); } catch (SchemaException e) { if (ModelExecuteOptions.isRaw(options)) { ModelUtils.recordPartialError(result, e); // just go on, this is raw, we need to continue even without complete schema } else { ModelUtils.recordFatalError(result, e); throw e; } } catch (ObjectNotFoundException e) { if (ModelExecuteOptions.isRaw(options)) { ModelUtils.recordPartialError(result, e); // just go on, this is raw, we need to continue even without complete schema } else { ModelUtils.recordFatalError(result, e); throw e; } } catch (CommunicationException e) { if (ModelExecuteOptions.isRaw(options)) { ModelUtils.recordPartialError(result, e); // just go on, this is raw, we need to continue even without complete schema } else { ModelUtils.recordFatalError(result, e); throw e; } } catch (ConfigurationException e) { if (ModelExecuteOptions.isRaw(options)) { ModelUtils.recordPartialError(result, e); // just go on, this is raw, we need to continue even without complete schema } else { ModelUtils.recordFatalError(result, e); throw e; } } } else { PrismObjectDefinition objDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(delta.getObjectTypeClass()); if (objDef == null) { throw new SchemaException("No definition for delta object type class: " + delta.getObjectTypeClass()); } delta.applyDefinition(objDef); } } } @Override public <T extends ObjectType> SearchResultList<PrismObject<T>> searchObjects(Class<T> type, ObjectQuery query, Collection<SelectorOptions<GetOperationOptions>> options, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { Validate.notNull(type, "Object type must not be null."); Validate.notNull(parentResult, "Operation result must not be null."); if (query != null) { ModelUtils.validatePaging(query.getPaging()); } GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options); ObjectTypes.ObjectManager searchProvider = ObjectTypes.getObjectManagerForClass(type); if (searchProvider == null || searchProvider == ObjectTypes.ObjectManager.MODEL || GetOperationOptions.isRaw(rootOptions)) { searchProvider = ObjectTypes.ObjectManager.REPOSITORY; } OperationResult result = parentResult.createSubresult(SEARCH_OBJECTS); result.addParams(new String[] { "query", "paging", "searchProvider" }, query, (query != null ? query.getPaging() : "undefined"), searchProvider); query = preProcessQuerySecurity(type, query); if (isFilterNone(query, result)) { return new SearchResultList(new ArrayList<>()); } SearchResultList<PrismObject<T>> list = null; try { RepositoryCache.enter(); logQuery(query); try { if (GetOperationOptions.isRaw(rootOptions)) { // MID-2218 QNameUtil.setTemporarilyTolerateUndeclaredPrefixes(true); } switch (searchProvider) { case REPOSITORY: list = cacheRepositoryService.searchObjects(type, query, options, result); break; case PROVISIONING: list = provisioning.searchObjects(type, query, options, task, result); break; case TASK_MANAGER: list = taskManager.searchObjects(type, query, options, result); if (workflowManager != null && TaskType.class.isAssignableFrom(type) && !GetOperationOptions.isRaw(rootOptions) && !GetOperationOptions.isNoFetch(rootOptions)) { workflowManager.augmentTaskObjectList(list, options, task, result); } break; default: throw new AssertionError("Unexpected search provider: " + searchProvider); } result.computeStatus(); result.cleanupResult(); } catch (CommunicationException | ConfigurationException | SchemaException | SecurityViolationException | RuntimeException | ObjectNotFoundException e) { processSearchException(e, rootOptions, searchProvider, result); throw e; } finally { QNameUtil.setTemporarilyTolerateUndeclaredPrefixes(false); if (LOGGER.isTraceEnabled()) { LOGGER.trace(result.dump(false)); } } if (list == null) { list = new SearchResultList(new ArrayList<PrismObject<T>>()); } for (PrismObject<T> object : list) { if (hookRegistry != null) { for (ReadHook hook : hookRegistry.getAllReadHooks()) { hook.invoke(object, options, task, result); } } resolve(object, options, task, result); } } finally { RepositoryCache.exit(); } // postprocessing objects that weren't handled by their correct provider (e.g. searching for ObjectType, and retrieving tasks, resources, shadows) // currently only resources and shadows are handled in this way // TODO generalize this approach somehow (something like "postprocess" in task/provisioning interface) if (searchProvider == ObjectTypes.ObjectManager.REPOSITORY && !GetOperationOptions.isRaw(rootOptions)) { for (PrismObject<T> object : list) { if (object.asObjectable() instanceof ResourceType || object.asObjectable() instanceof ShadowType) { provisioning.applyDefinition(object, result); } } } schemaTransformer.applySchemasAndSecurityToObjects(list, rootOptions, null, task, result); return list; } @Override public <T extends Containerable> SearchResultList<T> searchContainers( Class<T> type, ObjectQuery query, Collection<SelectorOptions<GetOperationOptions>> options, Task task, OperationResult parentResult) throws SchemaException, SecurityViolationException, ConfigurationException, ObjectNotFoundException { Validate.notNull(type, "Container value type must not be null."); Validate.notNull(parentResult, "Result type must not be null."); if (query != null) { ModelUtils.validatePaging(query.getPaging()); } final boolean isCase = AccessCertificationCaseType.class.equals(type); final boolean isWorkItem = WorkItemType.class.equals(type); if (!isCase && !isWorkItem) { throw new UnsupportedOperationException("searchContainers method is currently supported only for AccessCertificationCaseType and WorkItemType classes"); } final GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options); final OperationResult result = parentResult.createSubresult(SEARCH_CONTAINERS); result.addParams(new String[] { "type", "query", "paging" }, type, query, (query != null ? query.getPaging() : "undefined")); final ObjectTypes.ObjectManager manager; if (isCase) { query = preProcessSubobjectQuerySecurity(AccessCertificationCaseType.class, AccessCertificationCampaignType.class, query); manager = ObjectTypes.ObjectManager.REPOSITORY; } else if (isWorkItem) { query = preProcessWorkItemSecurity(query); manager = ObjectTypes.ObjectManager.WORKFLOW; } else { throw new IllegalStateException(); } if (isFilterNone(query, result)) { return new SearchResultList(new ArrayList<>()); } SearchResultList<T> list; try { RepositoryCache.enter(); logQuery(query); try { if (GetOperationOptions.isRaw(rootOptions)) { // MID-2218 QNameUtil.setTemporarilyTolerateUndeclaredPrefixes(true); } switch (manager) { case REPOSITORY: list = cacheRepositoryService.searchContainers(type, query, options, result); break; case WORKFLOW: list = workflowManager.searchContainers(type, query, options, result); break; default: throw new IllegalStateException(); } result.computeStatus(); result.cleanupResult(); } catch (SchemaException|RuntimeException e) { processSearchException(e, rootOptions, manager, result); throw e; } finally { QNameUtil.setTemporarilyTolerateUndeclaredPrefixes(false); if (LOGGER.isTraceEnabled()) { LOGGER.trace(result.dump(false)); } } if (list == null) { list = new SearchResultList(new ArrayList<>()); } for (T object : list) { // TODO implement read hook, if necessary resolve(object, options, task, result); } } finally { RepositoryCache.exit(); } if (isCase) { list = schemaTransformer.applySchemasAndSecurityToContainers(list, AccessCertificationCampaignType.class, AccessCertificationCampaignType.F_CASE, rootOptions, null, task, result); } else if (isWorkItem) { // TODO implement security post processing for WorkItems } else { throw new IllegalStateException(); } return list; } @Override public <T extends Containerable> Integer countContainers( Class<T> type, ObjectQuery query, Collection<SelectorOptions<GetOperationOptions>> options, Task task, OperationResult parentResult) throws SchemaException, SecurityViolationException { Validate.notNull(type, "Container value type must not be null."); Validate.notNull(parentResult, "Result type must not be null."); final boolean isWorkItem = WorkItemType.class.equals(type); if (!isWorkItem) { throw new UnsupportedOperationException("countContainers method is currently supported only for WorkItemType classes"); } final GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options); final OperationResult result = parentResult.createSubresult(SEARCH_CONTAINERS); result.addParams(new String[] { "type", "query"}, type, query); final ObjectTypes.ObjectManager manager; if (isWorkItem) { query = preProcessWorkItemSecurity(query); manager = ObjectTypes.ObjectManager.WORKFLOW; } else { throw new IllegalStateException(); } if (isFilterNone(query, result)) { return 0; } Integer count; try { RepositoryCache.enter(); logQuery(query); try { switch (manager) { //case REPOSITORY: list = cacheRepositoryService.searchContainers(type, query, options, result); break; case WORKFLOW: count = workflowManager.countContainers(type, query, options, result); break; default: throw new IllegalStateException(); } result.computeStatus(); result.cleanupResult(); } catch (SchemaException|RuntimeException e) { processSearchException(e, rootOptions, manager, result); throw e; } finally { if (LOGGER.isTraceEnabled()) { LOGGER.trace(result.dump(false)); } } } finally { RepositoryCache.exit(); } return count; } // // TODO - fix this temporary implementation (perhaps by storing 'groups' in user context on logon) // // TODO: currently we check only the direct assignments, we need to implement more complex mechanism // public List<PrismReferenceValue> getGroupsForUser(UserType user) { // List<PrismReferenceValue> retval = new ArrayList<>(); // for (AssignmentType assignmentType : user.getAssignment()) { // ObjectReferenceType ref = assignmentType.getTargetRef(); // if (ref != null) { // retval.add(ref.clone().asReferenceValue()); // } // } // return retval; // } private ObjectQuery preProcessWorkItemSecurity(ObjectQuery query) throws SchemaException, SecurityViolationException { // TODO uncomment the following, after our "query interpreter" will be able to interpret OR-clauses return query; // if (securityEnforcer.isAuthorized(ModelAuthorizationAction.READ_ALL_WORK_ITEMS.getUrl(), null, null, null, null, null)) { // return query; // } // ObjectFilter filter = query != null ? query.getFilter() : null; // UserType currentUser = securityEnforcer.getPrincipal().getUser(); // // ObjectFilter secFilter = QueryBuilder.queryFor(WorkItemType.class, getPrismContext()) // .item(WorkItemType.F_CANDIDATE_ROLES_REF).ref(getGroupsForUser(currentUser)) // .or().item(WorkItemType.F_ASSIGNEE_REF).ref(ObjectTypeUtil.createObjectRef(currentUser).asReferenceValue()) // .buildFilter(); // // return updateObjectQuery(query, // filter != null ? AndFilter.createAnd(filter, secFilter) : secFilter); } protected boolean isFilterNone(ObjectQuery query, OperationResult result) { if (query != null && query.getFilter() != null && query.getFilter() instanceof NoneFilter) { LOGGER.trace("Security denied the search"); result.recordStatus(OperationResultStatus.NOT_APPLICABLE, "Denied"); return true; } return false; } protected void logQuery(ObjectQuery query) { if (query != null){ if (query.getPaging() == null) { LOGGER.trace("Searching objects with null paging (query in TRACE)."); } else { LOGGER.trace("Searching objects from {} to {} ordered {} by {} (query in TRACE).", new Object[] { query.getPaging().getOffset(), query.getPaging().getMaxSize(), query.getPaging().getDirection(), query.getPaging().getOrderBy() }); } } } @Override public <T extends ObjectType> SearchResultMetadata searchObjectsIterative(Class<T> type, ObjectQuery query, final ResultHandler<T> handler, final Collection<SelectorOptions<GetOperationOptions>> options, final Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { Validate.notNull(type, "Object type must not be null."); Validate.notNull(parentResult, "Result type must not be null."); if (query != null) { ModelUtils.validatePaging(query.getPaging()); } final GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options); ObjectTypes.ObjectManager searchProvider = ObjectTypes.getObjectManagerForClass(type); if (searchProvider == null || searchProvider == ObjectTypes.ObjectManager.MODEL || GetOperationOptions.isRaw(rootOptions)) { searchProvider = ObjectTypes.ObjectManager.REPOSITORY; } final OperationResult result = parentResult.createSubresult(SEARCH_OBJECTS); result.addParams(new String[] { "query", "paging", "searchProvider" }, query, (query != null ? query.getPaging() : "undefined"), searchProvider); query = preProcessQuerySecurity(type, query); if (isFilterNone(query, result)) { return null; } ResultHandler<T> internalHandler = new ResultHandler<T>() { @Override public boolean handle(PrismObject<T> object, OperationResult parentResult) { try { object = object.cloneIfImmutable(); if (hookRegistry != null) { for (ReadHook hook : hookRegistry.getAllReadHooks()) { hook.invoke(object, options, task, result); // TODO result or parentResult??? [med] } } schemaTransformer.applySchemasAndSecurity(object, rootOptions, null, task, parentResult); } catch (SchemaException | ObjectNotFoundException | SecurityViolationException | CommunicationException | ConfigurationException ex) { parentResult.recordFatalError(ex); throw new SystemException(ex.getMessage(), ex); } return handler.handle(object, parentResult); } }; SearchResultMetadata metadata; try { RepositoryCache.enter(); logQuery(query); try { switch (searchProvider) { case REPOSITORY: metadata = cacheRepositoryService.searchObjectsIterative(type, query, internalHandler, options, false, result); break; // TODO move strictSequential flag to model API in some form case PROVISIONING: metadata = provisioning.searchObjectsIterative(type, query, options, internalHandler, task, result); break; case TASK_MANAGER: metadata = taskManager.searchObjectsIterative(type, query, options, internalHandler, result); break; default: throw new AssertionError("Unexpected search provider: " + searchProvider); } result.computeStatusIfUnknown(); result.cleanupResult(); } catch (CommunicationException e) { processSearchException(e, rootOptions, searchProvider, result); throw e; } catch (ConfigurationException e) { processSearchException(e, rootOptions, searchProvider, result); throw e; } catch (ObjectNotFoundException e) { processSearchException(e, rootOptions, searchProvider, result); throw e; } catch (SchemaException e) { processSearchException(e, rootOptions, searchProvider, result); throw e; } catch (SecurityViolationException e) { processSearchException(e, rootOptions, searchProvider, result); throw e; } catch (RuntimeException e) { processSearchException(e, rootOptions, searchProvider, result); throw e; } finally { if (LOGGER.isTraceEnabled()) { LOGGER.trace(result.dump(false)); } } } finally { RepositoryCache.exit(); } return metadata; } private void processSearchException(Exception e, GetOperationOptions rootOptions, ObjectTypes.ObjectManager searchProvider, OperationResult result) { String message; switch (searchProvider) { case REPOSITORY: message = "Couldn't search objects in repository"; break; case PROVISIONING: message = "Couldn't search objects in provisioning"; break; case TASK_MANAGER: message = "Couldn't search objects in task manager"; break; case WORKFLOW: message = "Couldn't search objects in workflow engine"; break; default: message = "Couldn't search objects"; break; // should not occur } LoggingUtils.logUnexpectedException(LOGGER, message, e); result.recordFatalError(message, e); result.cleanupResult(e); } @Override public <T extends ObjectType> Integer countObjects(Class<T> type, ObjectQuery query, Collection<SelectorOptions<GetOperationOptions>> options, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ConfigurationException, SecurityViolationException, CommunicationException { OperationResult result = parentResult.createMinorSubresult(COUNT_OBJECTS); result.addParams(new String[] { "query", "paging"}, query, (query != null ? query.getPaging() : "undefined")); query = preProcessQuerySecurity(type, query); if (isFilterNone(query, result)) { return 0; } Integer count; try { RepositoryCache.enter(); GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options); ObjectTypes.ObjectManager objectManager = ObjectTypes.getObjectManagerForClass(type); if (GetOperationOptions.isRaw(rootOptions) || objectManager == null || objectManager == ObjectTypes.ObjectManager.MODEL) { objectManager = ObjectTypes.ObjectManager.REPOSITORY; } switch (objectManager) { case PROVISIONING: count = provisioning.countObjects(type, query, options, task, parentResult); break; case REPOSITORY: count = cacheRepositoryService.countObjects(type, query, parentResult); break; case TASK_MANAGER: count = taskManager.countObjects(type, query, parentResult); break; default: throw new AssertionError("Unexpected objectManager: " + objectManager); } } catch (ConfigurationException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (SecurityViolationException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (SchemaException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (ObjectNotFoundException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (CommunicationException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (RuntimeException e) { ModelUtils.recordFatalError(result, e); throw e; } finally { RepositoryCache.exit(); } result.computeStatus(); result.cleanupResult(); return count; } @Override @Deprecated public PrismObject<UserType> findShadowOwner(String accountOid, Task task, OperationResult parentResult) throws ObjectNotFoundException, SecurityViolationException, SchemaException, ConfigurationException { Validate.notEmpty(accountOid, "Account oid must not be null or empty."); Validate.notNull(parentResult, "Result type must not be null."); RepositoryCache.enter(); PrismObject<UserType> user = null; LOGGER.trace("Listing account shadow owner for account with oid {}.", new Object[]{accountOid}); OperationResult result = parentResult.createSubresult(LIST_ACCOUNT_SHADOW_OWNER); result.addParams(new String[] { "accountOid" }, accountOid); try { user = cacheRepositoryService.listAccountShadowOwner(accountOid, result); result.recordSuccess(); } catch (ObjectNotFoundException ex) { LoggingUtils.logException(LOGGER, "Account with oid {} doesn't exists", ex, accountOid); result.recordFatalError("Account with oid '" + accountOid + "' doesn't exists", ex); throw ex; } catch (RuntimeException ex) { LoggingUtils.logException(LOGGER, "Couldn't list account shadow owner from repository" + " for account with oid {}", ex, accountOid); result.recordFatalError("Couldn't list account shadow owner for account with oid '" + accountOid + "'.", ex); throw ex; } catch (Error ex) { LoggingUtils.logException(LOGGER, "Couldn't list account shadow owner from repository" + " for account with oid {}", ex, accountOid); result.recordFatalError("Couldn't list account shadow owner for account with oid '" + accountOid + "'.", ex); throw ex; } finally { if (LOGGER.isTraceEnabled()) { LOGGER.trace(result.dump(false)); } RepositoryCache.exit(); result.cleanupResult(); } if (user != null) { try { user = user.cloneIfImmutable(); schemaTransformer.applySchemasAndSecurity(user, null, null, task, result); } catch (SchemaException | SecurityViolationException | ConfigurationException | ObjectNotFoundException ex) { LoggingUtils.logException(LOGGER, "Couldn't list account shadow owner from repository" + " for account with oid {}", ex, accountOid); result.recordFatalError("Couldn't list account shadow owner for account with oid '" + accountOid + "'.", ex); throw ex; } } return user; } @Override public PrismObject<? extends FocusType> searchShadowOwner(String shadowOid, Collection<SelectorOptions<GetOperationOptions>> options, Task task, OperationResult parentResult) throws ObjectNotFoundException, SecurityViolationException, SchemaException, ConfigurationException { Validate.notEmpty(shadowOid, "Account oid must not be null or empty."); Validate.notNull(parentResult, "Result type must not be null."); RepositoryCache.enter(); PrismObject<? extends FocusType> focus = null; LOGGER.trace("Listing account shadow owner for account with oid {}.", new Object[]{shadowOid}); OperationResult result = parentResult.createSubresult(LIST_ACCOUNT_SHADOW_OWNER); result.addParams(new String[] { "accountOid" }, shadowOid); try { focus = cacheRepositoryService.searchShadowOwner(shadowOid, options, result); result.recordSuccess(); } catch (RuntimeException ex) { LoggingUtils.logException(LOGGER, "Couldn't list account shadow owner from repository" + " for account with oid {}", ex, shadowOid); result.recordFatalError("Couldn't list account shadow owner for account with oid '" + shadowOid + "'.", ex); throw ex; } catch (Error ex) { LoggingUtils.logException(LOGGER, "Couldn't list account shadow owner from repository" + " for account with oid {}", ex, shadowOid); result.recordFatalError("Couldn't list account shadow owner for account with oid '" + shadowOid + "'.", ex); throw ex; } finally { if (LOGGER.isTraceEnabled()) { LOGGER.trace(result.dump(false)); } RepositoryCache.exit(); result.cleanupResult(); } if (focus != null) { try { focus = focus.cloneIfImmutable(); schemaTransformer.applySchemasAndSecurity(focus, null, null, task, result); } catch (SchemaException | SecurityViolationException | ConfigurationException | ObjectNotFoundException ex) { LoggingUtils.logException(LOGGER, "Couldn't list account shadow owner from repository" + " for account with oid {}", ex, shadowOid); result.recordFatalError("Couldn't list account shadow owner for account with oid '" + shadowOid + "'.", ex); throw ex; } } return focus; } @Override public List<PrismObject<? extends ShadowType>> listResourceObjects(String resourceOid, QName objectClass, ObjectPaging paging, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { Validate.notEmpty(resourceOid, "Resource oid must not be null or empty."); Validate.notNull(objectClass, "Object type must not be null."); Validate.notNull(paging, "Paging must not be null."); Validate.notNull(parentResult, "Result type must not be null."); ModelUtils.validatePaging(paging); RepositoryCache.enter(); List<PrismObject<? extends ShadowType>> list = null; try { LOGGER.trace( "Listing resource objects {} from resource, oid {}, from {} to {} ordered {} by {}.", new Object[] { objectClass, resourceOid, paging.getOffset(), paging.getMaxSize(), paging.getOrderBy(), paging.getDirection() }); OperationResult result = parentResult.createSubresult(LIST_RESOURCE_OBJECTS); result.addParams(new String[] { "resourceOid", "objectType", "paging" }, resourceOid, objectClass, paging); try { list = provisioning.listResourceObjects(resourceOid, objectClass, paging, task, result); } catch (SchemaException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (CommunicationException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (ConfigurationException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (SecurityViolationException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (ObjectNotFoundException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } result.recordSuccess(); result.cleanupResult(); if (list == null) { list = new ArrayList<PrismObject<? extends ShadowType>>(); } } finally { RepositoryCache.exit(); } return list; } // This returns OperationResult instead of taking it as in/out argument. // This is different // from the other methods. The testResource method is not using // OperationResult to track its own // execution but rather to track the execution of resource tests (that in // fact happen in provisioning). @Override public OperationResult testResource(String resourceOid, Task task) throws ObjectNotFoundException { Validate.notEmpty(resourceOid, "Resource oid must not be null or empty."); RepositoryCache.enter(); LOGGER.trace("Testing resource OID: {}", new Object[]{resourceOid}); OperationResult testResult = null; try { testResult = provisioning.testResource(resourceOid); } catch (ObjectNotFoundException ex) { LOGGER.error("Error testing resource OID: {}: Object not found: {} ", new Object[] { resourceOid, ex.getMessage(), ex }); RepositoryCache.exit(); throw ex; } catch (SystemException ex) { LOGGER.error("Error testing resource OID: {}: Object not found: {} ", new Object[] { resourceOid, ex.getMessage(), ex }); RepositoryCache.exit(); throw ex; } catch (Exception ex) { LOGGER.error("Error testing resource OID: {}: {} ", new Object[] { resourceOid, ex.getMessage(), ex }); RepositoryCache.exit(); throw new SystemException(ex.getMessage(), ex); } if (testResult != null) { LOGGER.debug("Finished testing resource OID: {}, result: {} ", resourceOid, testResult.getStatus()); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Test result:\n{}", testResult.dump(false)); } } else { LOGGER.error("Test resource returned null result"); } RepositoryCache.exit(); return testResult; } // Note: The result is in the task. No need to pass it explicitly @Override public void importFromResource(String resourceOid, QName objectClass, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { Validate.notEmpty(resourceOid, "Resource oid must not be null or empty."); Validate.notNull(objectClass, "Object class must not be null."); Validate.notNull(task, "Task must not be null."); RepositoryCache.enter(); LOGGER.trace("Launching import from resource with oid {} for object class {}.", new Object[]{ resourceOid, objectClass}); OperationResult result = parentResult.createSubresult(IMPORT_ACCOUNTS_FROM_RESOURCE); result.addParam("resourceOid", resourceOid); result.addParam("objectClass", objectClass); result.addArbitraryObjectAsParam("task", task); // TODO: add context to the result // Fetch resource definition from the repo/provisioning ResourceType resource = null; try { resource = getObject(ResourceType.class, resourceOid, null, task, result).asObjectable(); if (resource.getSynchronization() == null || resource.getSynchronization().getObjectSynchronization().isEmpty()) { OperationResult subresult = result.createSubresult(IMPORT_ACCOUNTS_FROM_RESOURCE+".check"); subresult.recordWarning("No synchronization settings in "+resource+", import will probably do nothing"); LOGGER.warn("No synchronization settings in "+resource+", import will probably do nothing"); } else { ObjectSynchronizationType syncType = resource.getSynchronization().getObjectSynchronization().iterator().next(); if (syncType.isEnabled() != null && !syncType.isEnabled()) { OperationResult subresult = result.createSubresult(IMPORT_ACCOUNTS_FROM_RESOURCE+".check"); subresult.recordWarning("Synchronization is disabled for "+resource+", import will probably do nothing"); LOGGER.warn("Synchronization is disabled for "+resource+", import will probably do nothing"); } } result.recordStatus(OperationResultStatus.IN_PROGRESS, "Task running in background"); importAccountsFromResourceTaskHandler.launch(resource, objectClass, task, result); // The launch should switch task to asynchronous. It is in/out, so no // other action is needed if (!task.isAsynchronous()) { result.recordSuccess(); } result.cleanupResult(); } catch (ObjectNotFoundException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (CommunicationException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (ConfigurationException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (SecurityViolationException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (RuntimeException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } finally { RepositoryCache.exit(); } } @Override public void importFromResource(String shadowOid, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, SecurityViolationException { Validate.notNull(shadowOid, "Shadow OID must not be null."); Validate.notNull(task, "Task must not be null."); RepositoryCache.enter(); LOGGER.trace("Launching importing shadow {} from resource.", shadowOid); OperationResult result = parentResult.createSubresult(IMPORT_ACCOUNTS_FROM_RESOURCE); result.addParam(OperationResult.PARAM_OID, shadowOid); result.addArbitraryObjectAsParam("task", task); // TODO: add context to the result try { boolean wasOk = importAccountsFromResourceTaskHandler.importSingleShadow(shadowOid, task, result); if (wasOk) { result.recordSuccess(); } else { // the error should be in the result already, compute should reveal that to the top-level result.computeStatus(); } result.cleanupResult(); } catch (ObjectNotFoundException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (CommunicationException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (ConfigurationException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (SecurityViolationException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } catch (RuntimeException ex) { ModelUtils.recordFatalError(result, ex); throw ex; } finally { RepositoryCache.exit(); } } @Override public void importObjectsFromFile(File input, ImportOptionsType options, Task task, OperationResult parentResult) throws FileNotFoundException { OperationResult result = parentResult.createSubresult(IMPORT_OBJECTS_FROM_FILE); FileInputStream fis = null; try { fis = new FileInputStream(input); } catch (FileNotFoundException e) { IOUtils.closeQuietly(fis); String msg = "Error reading from file " + input + ": " + e.getMessage(); result.recordFatalError(msg, e); throw e; } try { importObjectsFromStream(fis, options, task, parentResult); } catch (RuntimeException e) { result.recordFatalError(e); throw e; } finally { try { fis.close(); } catch (IOException e) { LOGGER.error("Error closing file " + input + ": " + e.getMessage(), e); } } result.computeStatus(); } @Override public void importObjectsFromStream(InputStream input, ImportOptionsType options, Task task, OperationResult parentResult) { RepositoryCache.enter(); OperationResult result = parentResult.createSubresult(IMPORT_OBJECTS_FROM_STREAM); result.addParam("options", options); objectImporter.importObjects(input, options, task, result); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Import result:\n{}", result.debugDump()); } // No need to compute status. The validator inside will do it. // result.computeStatus("Couldn't import object from input stream."); RepositoryCache.exit(); result.cleanupResult(); } /* * (non-Javadoc) * * @see * com.evolveum.midpoint.model.api.ModelService#discoverConnectors(com.evolveum * .midpoint.xml.ns._public.common.common_1.ConnectorHostType, * com.evolveum.midpoint.common.result.OperationResult) */ @Override public Set<ConnectorType> discoverConnectors(ConnectorHostType hostType, Task task, OperationResult parentResult) throws CommunicationException, SecurityViolationException, SchemaException, ConfigurationException, ObjectNotFoundException { RepositoryCache.enter(); OperationResult result = parentResult.createSubresult(DISCOVER_CONNECTORS); Set<ConnectorType> discoverConnectors; try { discoverConnectors = provisioning.discoverConnectors(hostType, result); } catch (CommunicationException e) { result.recordFatalError(e.getMessage(), e); RepositoryCache.exit(); throw e; } List<ConnectorType> connectorList = new ArrayList<>(discoverConnectors); schemaTransformer.applySchemasAndSecurityToObjectTypes(connectorList, null, null, task, result); result.computeStatus("Connector discovery failed"); RepositoryCache.exit(); result.cleanupResult(); return new HashSet<>(connectorList); } /* * (non-Javadoc) * * @see * com.evolveum.midpoint.model.api.ModelService#initialize(com.evolveum. * midpoint.common.result.OperationResult) */ @Override public void postInit(OperationResult parentResult) { systemObjectCache.invalidateCaches(); // necessary for testing situations where we re-import different system configurations with the same version (on system init) RepositoryCache.enter(); OperationResult result = parentResult.createSubresult(POST_INIT); result.addContext(OperationResult.CONTEXT_IMPLEMENTATION_CLASS, ModelController.class); securityEnforcer.setUserProfileService(userProfileService); // TODO: initialize repository // repository (including logging config) is initialized in its own method taskManager.postInit(result); // Initialize provisioning provisioning.postInit(result); if (result.isUnknown()) { result.computeStatus(); } RepositoryCache.exit(); result.cleanupResult(); } @Override public <T extends ObjectType> CompareResultType compareObject(PrismObject<T> provided, Collection<SelectorOptions<GetOperationOptions>> readOptions, ModelCompareOptions compareOptions, @NotNull List<ItemPath> ignoreItems, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException { Validate.notNull(provided, "Object must not be null or empty."); Validate.notNull(parentResult, "Operation result must not be null."); OperationResult result = parentResult.createMinorSubresult(COMPARE_OBJECT); result.addParam("oid", provided.getOid()); result.addParam("name", provided.getName()); result.addCollectionOfSerializablesAsParam("readOptions", readOptions); result.addParam("compareOptions", compareOptions); result.addCollectionOfSerializablesAsParam("ignoreItems", ignoreItems); CompareResultType rv = new CompareResultType(); try { boolean c2p = ModelCompareOptions.isComputeCurrentToProvided(compareOptions); boolean p2c = ModelCompareOptions.isComputeProvidedToCurrent(compareOptions); boolean returnC = ModelCompareOptions.isReturnCurrent(compareOptions); boolean returnP = ModelCompareOptions.isReturnNormalized(compareOptions); boolean ignoreOperational = ModelCompareOptions.isIgnoreOperationalItems(compareOptions); if (!c2p && !p2c && !returnC && !returnP) { return rv; } PrismObject<T> current = null; if (c2p || p2c || returnC) { current = fetchCurrentObject(provided.getCompileTimeClass(), provided.getOid(), provided.getName(), readOptions, task, result); removeIgnoredItems(current, ignoreItems); if (ignoreOperational) { removeOperationalItems(current); } } removeIgnoredItems(provided, ignoreItems); if (ignoreOperational) { removeOperationalItems(provided); } if (c2p) { rv.setCurrentToProvided(DeltaConvertor.toObjectDeltaType(DiffUtil.diff(current, provided))); } if (p2c) { rv.setProvidedToCurrent(DeltaConvertor.toObjectDeltaType(DiffUtil.diff(provided, current))); } if (returnC && current != null) { rv.setCurrentObject(current.asObjectable()); } if (returnP) { rv.setNormalizedObject(provided.asObjectable()); } } finally { result.computeStatus(); result.cleanupResult(); } return rv; } private <T extends ObjectType> PrismObject<T> fetchCurrentObject(Class<T> type, String oid, PolyString name, Collection<SelectorOptions<GetOperationOptions>> readOptions, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { if (readOptions == null) { readOptions = new ArrayList<>(); } GetOperationOptions root = SelectorOptions.findRootOptions(readOptions); if (root == null) { readOptions.add(SelectorOptions.create(GetOperationOptions.createAllowNotFound())); } else { root.setAllowNotFound(true); } if (oid != null) { try { return getObject(type, oid, readOptions, task, result); } catch (ObjectNotFoundException e) { return null; } } if (name == null || name.getOrig() == null) { throw new IllegalArgumentException("Neither OID nor name of the object is known."); } ObjectQuery nameQuery = QueryBuilder.queryFor(type, prismContext) .item(ObjectType.F_NAME).eqPoly(name.getOrig()) .build(); List<PrismObject<T>> objects = searchObjects(type, nameQuery, readOptions, task, result); if (objects.isEmpty()) { return null; } else if (objects.size() == 1) { return objects.get(0); } else { throw new SchemaException("More than 1 object of type " + type + " with the name of " + name + ": There are " + objects.size() + " of them."); } } private <T extends ObjectType> void removeIgnoredItems(PrismObject<T> object, List<ItemPath> ignoreItems) { if (object == null) { return; } for (ItemPath path : ignoreItems) { Item item = object.findItem(path); // reduce to "removeItem" after fixing that method implementation if (item != null) { object.removeItem(item.getPath(), Item.class); } } } // TODO write in cleaner way private <T extends ObjectType> void removeOperationalItems(PrismObject<T> object) { if (object == null) { return; } final List<ItemPath> operationalItems = new ArrayList<>(); object.accept(new Visitor() { @Override public void visit(Visitable visitable) { if (visitable instanceof Item) { Item item = ((Item) visitable); if (item.getDefinition() != null && item.getDefinition().isOperational()) { operationalItems.add(item.getPath()); // it would be nice if we could stop visiting children here but that's not possible now } } } }); LOGGER.trace("Operational items: {}", operationalItems); removeIgnoredItems(object, operationalItems); } private <O extends ObjectType> ObjectQuery preProcessQuerySecurity(Class<O> objectType, ObjectQuery origQuery) throws SchemaException { ObjectFilter origFilter = null; if (origQuery != null) { origFilter = origQuery.getFilter(); } ObjectFilter secFilter = securityEnforcer.preProcessObjectFilter(ModelAuthorizationAction.READ.getUrl(), null, objectType, null, origFilter); return updateObjectQuery(origQuery, secFilter); } // we expect that objectType is a direct parent of containerType private <C extends Containerable, O extends ObjectType> ObjectQuery preProcessSubobjectQuerySecurity(Class<C> containerType, Class<O> objectType, ObjectQuery origQuery) throws SchemaException { ObjectFilter secParentFilter = securityEnforcer.preProcessObjectFilter(ModelAuthorizationAction.READ.getUrl(), null, objectType, null, null); if (secParentFilter == null || secParentFilter instanceof AllFilter) { return origQuery; // no need to update the query } ObjectFilter secChildFilter; if (secParentFilter instanceof NoneFilter) { secChildFilter = NoneFilter.createNone(); } else { ObjectFilter origChildFilter = origQuery != null ? origQuery.getFilter() : null; ObjectFilter secChildFilterParentPart = ExistsFilter.createExists(new ItemPath(PrismConstants.T_PARENT), containerType, prismContext, secParentFilter); if (origChildFilter == null) { secChildFilter = secChildFilterParentPart; } else { secChildFilter = AndFilter.createAnd(origChildFilter, secChildFilterParentPart); } } return updateObjectQuery(origQuery, secChildFilter); } private ObjectQuery updateObjectQuery(ObjectQuery origQuery, ObjectFilter updatedFilter) { if (origQuery != null) { origQuery.setFilter(updatedFilter); return origQuery; } else if (updatedFilter == null) { return null; } else { ObjectQuery objectQuery = new ObjectQuery(); objectQuery.setFilter(updatedFilter); return objectQuery; } } //region Task-related operations @Override public boolean suspendTasks(Collection<String> taskOids, long waitForStop, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException { authorizeTaskCollectionOperation(ModelAuthorizationAction.SUSPEND_TASK, taskOids, parentResult); return taskManager.suspendTasks(taskOids, waitForStop, parentResult); } @Override public void suspendAndDeleteTasks(Collection<String> taskOids, long waitForStop, boolean alsoSubtasks, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException { authorizeTaskCollectionOperation(ModelAuthorizationAction.DELETE, taskOids, parentResult); taskManager.suspendAndDeleteTasks(taskOids, waitForStop, alsoSubtasks, parentResult); } @Override public void resumeTasks(Collection<String> taskOids, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException { authorizeTaskCollectionOperation(ModelAuthorizationAction.RESUME_TASK, taskOids, parentResult); taskManager.resumeTasks(taskOids, parentResult); } @Override public void scheduleTasksNow(Collection<String> taskOids, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException { authorizeTaskCollectionOperation(ModelAuthorizationAction.RUN_TASK_IMMEDIATELY, taskOids, parentResult); taskManager.scheduleTasksNow(taskOids, parentResult); } @Override public PrismObject<TaskType> getTaskByIdentifier(String identifier, Collection<SelectorOptions<GetOperationOptions>> options, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ConfigurationException, SecurityViolationException { PrismObject<TaskType> task = taskManager.getTaskTypeByIdentifier(identifier, options, parentResult); GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options); task = task.cloneIfImmutable(); schemaTransformer.applySchemasAndSecurity(task, rootOptions, null, null, parentResult); return task; } @Override public boolean deactivateServiceThreads(long timeToWait, OperationResult parentResult) throws SchemaException, SecurityViolationException { securityEnforcer.authorize(ModelAuthorizationAction.STOP_SERVICE_THREADS.getUrl(), null, null, null, null, null, parentResult); return taskManager.deactivateServiceThreads(timeToWait, parentResult); } @Override public void reactivateServiceThreads(OperationResult parentResult) throws SchemaException, SecurityViolationException { securityEnforcer.authorize(ModelAuthorizationAction.START_SERVICE_THREADS.getUrl(), null, null, null, null, null, parentResult); taskManager.reactivateServiceThreads(parentResult); } @Override public boolean getServiceThreadsActivationState() { return taskManager.getServiceThreadsActivationState(); } @Override public void stopSchedulers(Collection<String> nodeIdentifiers, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException { authorizeNodeCollectionOperation(ModelAuthorizationAction.STOP_TASK_SCHEDULER, nodeIdentifiers, parentResult); taskManager.stopSchedulers(nodeIdentifiers, parentResult); } @Override public boolean stopSchedulersAndTasks(Collection<String> nodeIdentifiers, long waitTime, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException { authorizeNodeCollectionOperation(ModelAuthorizationAction.STOP_TASK_SCHEDULER, nodeIdentifiers, parentResult); return taskManager.stopSchedulersAndTasks(nodeIdentifiers, waitTime, parentResult); } @Override public void startSchedulers(Collection<String> nodeIdentifiers, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException { authorizeNodeCollectionOperation(ModelAuthorizationAction.START_TASK_SCHEDULER, nodeIdentifiers, parentResult); taskManager.startSchedulers(nodeIdentifiers, parentResult); } @Override public void synchronizeTasks(OperationResult parentResult) throws SchemaException, SecurityViolationException { securityEnforcer.authorize(ModelAuthorizationAction.SYNCHRONIZE_TASKS.getUrl(), null, null, null, null, null, parentResult); taskManager.synchronizeTasks(parentResult); } @Override public void synchronizeWorkflowRequests(OperationResult parentResult) throws SchemaException, SecurityViolationException { securityEnforcer.authorize(ModelAuthorizationAction.SYNCHRONIZE_WORKFLOW_REQUESTS.getUrl(), null, null, null, null, null, parentResult); workflowManager.synchronizeWorkflowRequests(parentResult); } @Override public List<String> getAllTaskCategories() { return taskManager.getAllTaskCategories(); } @Override public String getHandlerUriForCategory(String category) { return taskManager.getHandlerUriForCategory(category); } private void authorizeTaskCollectionOperation(ModelAuthorizationAction action, Collection<String> oids, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, SecurityViolationException { if (securityEnforcer.isAuthorized(AuthorizationConstants.AUTZ_ALL_URL, null, null, null, null, null)) { return; } for (String oid : oids) { PrismObject existingObject = null; try { existingObject = cacheRepositoryService.getObject(TaskType.class, oid, null, parentResult); } catch (ObjectNotFoundException|SchemaException e) { throw e; } securityEnforcer.authorize(action.getUrl(), null, existingObject, null, null, null, parentResult); } } private void authorizeNodeCollectionOperation(ModelAuthorizationAction action, Collection<String> identifiers, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, SecurityViolationException { if (securityEnforcer.isAuthorized(AuthorizationConstants.AUTZ_ALL_URL, null, null, null, null, null)) { return; } for (String identifier : identifiers) { PrismObject existingObject = null; try { ObjectQuery q = ObjectQueryUtil.createNameQuery(NodeType.class, prismContext, identifier); List<PrismObject<NodeType>> nodes = cacheRepositoryService.searchObjects(NodeType.class, q, null, parentResult); if (nodes.isEmpty()) { throw new ObjectNotFoundException("Node with identifier '" + identifier + "' couldn't be found."); } else if (nodes.size() > 1) { throw new SystemException("Multiple nodes with identifier '" + identifier + "'"); } existingObject = nodes.get(0); } catch (ObjectNotFoundException|SchemaException e) { throw e; } securityEnforcer.authorize(action.getUrl(), null, existingObject, null, null, null, parentResult); } } //endregion //region Workflow-related operations @Override public void completeWorkItem(String workItemId, boolean decision, String comment, ObjectDelta additionalDelta, OperationResult parentResult) throws SecurityViolationException, SchemaException { getWorkflowManagerChecked().completeWorkItem(workItemId, decision, comment, additionalDelta, null, parentResult); } @Override public void stopProcessInstance(String instanceId, String username, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, SecurityViolationException { if (!securityEnforcer.isAuthorized(AuthorizationConstants.AUTZ_ALL_URL, null, null, null, null, null)) { ObjectQuery query = QueryBuilder.queryFor(TaskType.class, prismContext) .item(TaskType.F_WORKFLOW_CONTEXT, WfContextType.F_PROCESS_INSTANCE_ID).eq(instanceId) .build(); List<PrismObject<TaskType>> tasks = cacheRepositoryService.searchObjects(TaskType.class, query, GetOperationOptions.createRawCollection(), parentResult); if (tasks.size() > 1) { throw new IllegalStateException("More than one task for process instance ID " + instanceId); } else if (tasks.size() == 0) { throw new ObjectNotFoundException("No task for process instance ID " + instanceId, instanceId); } securityEnforcer.authorize(ModelAuthorizationAction.STOP_APPROVAL_PROCESS_INSTANCE.getUrl(), null, tasks.get(0), null, null, null, parentResult); } getWorkflowManagerChecked().stopProcessInstance(instanceId, username, parentResult); } @Override public void claimWorkItem(String workItemId, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException { getWorkflowManagerChecked().claimWorkItem(workItemId, parentResult); } @Override public void releaseWorkItem(String workItemId, OperationResult parentResult) throws ObjectNotFoundException, SecurityViolationException { getWorkflowManagerChecked().releaseWorkItem(workItemId, parentResult); } @Override public void delegateWorkItem(String workItemId, List<ObjectReferenceType> delegates, WorkItemDelegationMethodType method, OperationResult parentResult) throws ObjectNotFoundException, SecurityViolationException, SchemaException { getWorkflowManagerChecked().delegateWorkItem(workItemId, delegates, method, parentResult); } @Override public void cleanupActivitiProcesses(OperationResult parentResult) throws SchemaException, SecurityViolationException { securityEnforcer.authorize(ModelAuthorizationAction.CLEANUP_PROCESS_INSTANCES.getUrl(), null, null, null, null, null, parentResult); getWorkflowManagerChecked().cleanupActivitiProcesses(parentResult); } //endregion //region Scripting (bulk actions) @Deprecated @Override public void evaluateExpressionInBackground(QName objectType, ObjectFilter filter, String actionName, Task task, OperationResult parentResult) throws SchemaException, SecurityViolationException { checkScriptingAuthorization(parentResult); scriptingExpressionEvaluator.evaluateExpressionInBackground(objectType, filter, actionName, task, parentResult); } @Override public void evaluateExpressionInBackground(ScriptingExpressionType expression, Task task, OperationResult parentResult) throws SchemaException, SecurityViolationException { checkScriptingAuthorization(parentResult); scriptingExpressionEvaluator.evaluateExpressionInBackground(expression, task, parentResult); } @Override public ScriptExecutionResult evaluateExpression(ScriptingExpressionType expression, Task task, OperationResult result) throws ScriptExecutionException, SchemaException, SecurityViolationException { checkScriptingAuthorization(result); ExecutionContext executionContext = scriptingExpressionEvaluator.evaluateExpression(expression, task, result); return executionContext.toExecutionResult(); } @Override public ScriptExecutionResult evaluateExpression(ExecuteScriptType scriptExecutionCommand, Task task, OperationResult result) throws ScriptExecutionException, SchemaException, SecurityViolationException { checkScriptingAuthorization(result); ExecutionContext executionContext = scriptingExpressionEvaluator.evaluateExpression(scriptExecutionCommand, task, result); return executionContext.toExecutionResult(); } private void checkScriptingAuthorization(OperationResult parentResult) throws SchemaException, SecurityViolationException { securityEnforcer.authorize(ModelAuthorizationAction.EXECUTE_SCRIPT.getUrl(), null, null, null, null, null, parentResult); } //endregion //region Certification @Override public AccessCertificationCasesStatisticsType getCampaignStatistics(String campaignOid, boolean currentStageOnly, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, SecurityViolationException, ObjectAlreadyExistsException { return getCertificationManagerChecked().getCampaignStatistics(campaignOid, currentStageOnly, task, parentResult); } @Override public void recordDecision(String campaignOid, long caseId, long workItemId, AccessCertificationResponseType response, String comment, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, SecurityViolationException, ObjectAlreadyExistsException { getCertificationManagerChecked().recordDecision(campaignOid, caseId, workItemId, response, comment, task, parentResult); } @Deprecated @Override public List<AccessCertificationWorkItemType> searchOpenWorkItems(ObjectQuery baseWorkItemsQuery, boolean notDecidedOnly, Collection<SelectorOptions<GetOperationOptions>> options, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, SecurityViolationException { return getCertificationManagerChecked().searchOpenWorkItems(baseWorkItemsQuery, notDecidedOnly, options, task, parentResult); } @Override public void closeCampaign(String campaignOid, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, SecurityViolationException, ObjectAlreadyExistsException { getCertificationManagerChecked().closeCampaign(campaignOid, task, result); } @Override public void startRemediation(String campaignOid, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, SecurityViolationException, ObjectAlreadyExistsException { getCertificationManagerChecked().startRemediation(campaignOid, task, result); } @Override public void closeCurrentStage(String campaignOid, int stageNumber, Task task, OperationResult parentResult) throws SchemaException, SecurityViolationException, ObjectNotFoundException, ObjectAlreadyExistsException { getCertificationManagerChecked().closeCurrentStage(campaignOid, stageNumber, task, parentResult); } @Override public void openNextStage(String campaignOid, int stageNumber, Task task, OperationResult parentResult) throws SchemaException, SecurityViolationException, ObjectNotFoundException, ObjectAlreadyExistsException { getCertificationManagerChecked().openNextStage(campaignOid, stageNumber, task, parentResult); } @Override public AccessCertificationCampaignType createCampaign(String definitionOid, Task task, OperationResult parentResult) throws SchemaException, SecurityViolationException, ObjectNotFoundException, ObjectAlreadyExistsException { return getCertificationManagerChecked().createCampaign(definitionOid, task, parentResult); } //endregion @Override public <O extends ObjectType> Collection<ObjectDeltaOperation<? extends ObjectType>> mergeObjects(Class<O> type, String leftOid, String rightOid, String mergeConfigurationName, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, ConfigurationException, ObjectAlreadyExistsException, ExpressionEvaluationException, CommunicationException, PolicyViolationException, SecurityViolationException { OperationResult result = parentResult.createSubresult(MERGE_OBJECTS); result.addParam("leftOid", leftOid); result.addParam("rightOid", rightOid); result.addParam("class", type); RepositoryCache.enter(); try { Collection<ObjectDeltaOperation<? extends ObjectType>> deltas = objectMerger.mergeObjects(type, leftOid, rightOid, mergeConfigurationName, task, result); result.computeStatus(); return deltas; } catch (ObjectNotFoundException | SchemaException | ConfigurationException | ObjectAlreadyExistsException | ExpressionEvaluationException | CommunicationException | PolicyViolationException | SecurityViolationException e) { ModelUtils.recordFatalError(result, e); throw e; } catch (RuntimeException | Error e) { ModelUtils.recordFatalError(result, e); throw e; } finally { QNameUtil.setTemporarilyTolerateUndeclaredPrefixes(false); RepositoryCache.exit(); } } }