Skip to content

Commit

Permalink
Add authorization checks in ModelController
Browse files Browse the repository at this point in the history
This resolves MID-9460.
  • Loading branch information
mederly committed Feb 8, 2024
1 parent 9a21a1b commit b83016c
Show file tree
Hide file tree
Showing 17 changed files with 296 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ public enum ModelAuthorizationAction implements DisplayableValue<String> {
EXECUTE_SCRIPT("executeScript", "Execute script", "EXECUTE_SCRIPT_HELP"),
CHANGE_CREDENTIALS("changeCredentials", "Change credentials", "CHANGE_CREDENTIALS_HELP"),

NOTIFY_CHANGE("notifyChange", "Notify change", "NOTIFY_CHANGE_HELP"),

SUSPEND_TASK("suspendTask", "Suspend task", "SUSPEND_TASK_HELP"),
RESUME_TASK("resumeTask", "Resume task", "RESUME_TASK_HELP"),
RUN_TASK_IMMEDIATELY("runTaskImmediately", "Run task immediately", "RUN_TASK_IMMEDIATELY_HELP"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -505,37 +505,33 @@ default <T extends ObjectType> Integer countObjects(TypedQuery<T> query,
return countObjects(query.getType(), query.toObjectQuery(), query.getOptions(), task, parentResult);
}


default <T extends Containerable> Integer countContainers(TypedQuery<T> query,
Task task, OperationResult parentResult) throws SchemaException,
ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException {
return countContainers(query.getType(), query.toObjectQuery(), query.getOptions(), task, parentResult);
}


/**
* <p>
* Test the resource connection and basic resource connector functionality.
* </p>
* <p>
*
* Authorizations are checked here.
*
* Work same as {@link ProvisioningService#testResource(PrismObject, Task, OperationResult)}.
* </p>
*
* @param resourceOid OID of resource to test
* @return results of executed tests
* @throws ObjectNotFoundException specified object does not exist
* @throws IllegalArgumentException wrong OID format
*/
OperationResult testResource(String resourceOid, Task task, OperationResult parentResult)
throws ObjectNotFoundException, SchemaException, ConfigurationException;
throws ObjectNotFoundException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException, CommunicationException;

/**
* <p>
* Test the resource connection and basic resource connector functionality.
* </p>
* <p>
* Work same as {@link com.evolveum.midpoint.provisioning.api.ProvisioningService#testResource(PrismObject, Task, OperationResult)}.
* </p>
*
* Work same as {@link ProvisioningService#testResource(PrismObject, Task, OperationResult)}.
*
* For internal use. Authorizations are NOT checked here!
*
* @param resource resource to test
* @return results of executed tests
Expand All @@ -544,15 +540,13 @@ OperationResult testResource(String resourceOid, Task task, OperationResult pare
OperationResult testResource(PrismObject<ResourceType> resource, Task task, OperationResult parentResult)
throws ObjectNotFoundException, SchemaException, ConfigurationException;


/**
* <p>
* Test partial resource connector configuration. Testing only basic connection.
* </p>
* <p>
*
* Method work with OperationResult same as method
* {@link ProvisioningService#testResource(PrismObject, Task, OperationResult)}.
* </p>
*
* For internal use. Authorizations are NOT checked here!
*
* @param resource resource to test
* @return results of executed partial test
Expand All @@ -575,7 +569,7 @@ OperationResult testResourcePartialConfiguration(PrismObject<ResourceType> resou
/**
* <p>
* Method work same as
* {@link com.evolveum.midpoint.provisioning.api.ProvisioningService#fetchSchema(PrismObject, OperationResult)}.
* {@link ProvisioningService#fetchSchema(PrismObject, OperationResult)}.
* </p>
*
* @param resource resource with connector configuration
Expand All @@ -586,7 +580,7 @@ OperationResult testResourcePartialConfiguration(PrismObject<ResourceType> resou
/**
* <p>
* Method work same as
* {@link com.evolveum.midpoint.provisioning.api.ProvisioningService#getNativeCapabilities(String, OperationResult)}.
* {@link ProvisioningService#getNativeCapabilities(String, OperationResult)}.
* </p>
*
* EXPERIMENTAL feature.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -857,7 +857,8 @@ <T extends ObjectType> int countObjects(Class<T> type, ObjectQuery query)
* @throws IllegalArgumentException
* wrong OID format
*/
OperationResult testResource(String resourceOid) throws ObjectNotFoundException, SchemaException, ConfigurationException;
OperationResult testResource(String resourceOid) throws ObjectNotFoundException, SchemaException, ConfigurationException,
SecurityViolationException, ExpressionEvaluationException, CommunicationException;

List<String> toList(String... s);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import com.evolveum.midpoint.model.api.BulkActionExecutionOptions;
import com.evolveum.midpoint.model.impl.scripting.BulkActionsExecutor;
import com.evolveum.midpoint.schema.config.ExecuteScriptConfigItem;
import com.evolveum.midpoint.schema.util.AccessCertificationWorkItemId;
import com.evolveum.midpoint.schema.util.*;
import com.evolveum.midpoint.model.impl.simulation.ProcessedObjectImpl;

import com.evolveum.midpoint.security.api.SecurityUtil;
Expand Down Expand Up @@ -77,9 +77,6 @@
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.ObjectTypeUtil;
import com.evolveum.midpoint.schema.util.WorkItemId;
import com.evolveum.midpoint.schema.util.cases.ApprovalUtils;
import com.evolveum.midpoint.security.api.SecurityContextManager;
import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters;
Expand Down Expand Up @@ -125,6 +122,7 @@ public class ModelController implements ModelService, TaskService, CaseService,
static final String RESOLVE_REFERENCE = CLASS_NAME_WITH_DOT + "resolveReference";
private static final String OP_APPLY_PROVISIONING_DEFINITION = CLASS_NAME_WITH_DOT + "applyProvisioningDefinition";
static final String OP_REEVALUATE_SEARCH_FILTERS = CLASS_NAME_WITH_DOT + "reevaluateSearchFilters";
static final String OP_AUTHORIZE_CHANGE_EXECUTION_START = CLASS_NAME_WITH_DOT + "authorizeChangeExecutionStart";

private static final int OID_GENERATION_ATTEMPTS = 5;

Expand Down Expand Up @@ -153,6 +151,7 @@ public class ModelController implements ModelService, TaskService, CaseService,
@Autowired private ObjectMerger objectMerger;
@Autowired private SystemObjectCache systemObjectCache;
@Autowired private ClockworkMedic clockworkMedic;
@Autowired private ClockworkAuditHelper clockworkAuditHelper;
@Autowired private EventDispatcher dispatcher;
@Autowired
@Qualifier("cacheRepositoryService")
Expand Down Expand Up @@ -337,7 +336,7 @@ private Collection<ObjectDeltaOperation<? extends ObjectType>> executeChangesNon

LensContext<? extends ObjectType> context = contextFactory.createContext(deltas, options, task, result);

authorizePartialExecution(context, options, task, result);
authorizeExecutionStart(context, options, task, result);

if (ModelExecuteOptions.isReevaluateSearchFilters(options)) {
String m = "ReevaluateSearchFilters option is not fully supported for non-raw operations yet. "
Expand Down Expand Up @@ -422,19 +421,46 @@ static void checkIndestructible(ObjectType object)
}
}

private void authorizePartialExecution(LensContext<? extends ObjectType> context,
ModelExecuteOptions options, Task task, OperationResult result)
throws SecurityViolationException, SchemaException, ObjectNotFoundException,
ExpressionEvaluationException, CommunicationException, ConfigurationException {
PartialProcessingOptionsType partialProcessing = ModelExecuteOptions.getPartialProcessing(options);
if (partialProcessing != null) {
PrismObject<? extends ObjectType> object = context.getFocusContext().getObjectAny();
// FIXME the information about the object may be incomplete (orgs, tenants, roles) but we treat it as complete here.
// See also MID-9454.
// TODO audit the request failure if this check fails
securityEnforcer.authorize(
ModelAuthorizationAction.PARTIAL_EXECUTION.getUrl(),
null, AuthorizationParameters.Builder.buildObject(object), task, result);
/**
* This is not a complete authorization! Here we just check if the user is roughly authorized to request the operation
* to be started, along with authorization for partial processing.
*/
private void authorizeExecutionStart(
LensContext<? extends ObjectType> context, ModelExecuteOptions options, Task task, OperationResult parentResult)
throws SchemaException, ExpressionEvaluationException, CommunicationException, SecurityViolationException,
ConfigurationException, ObjectNotFoundException {
List<String> relevantActions = List.of(
ModelAuthorizationAction.ADD.getUrl(),
ModelAuthorizationAction.MODIFY.getUrl(),
ModelAuthorizationAction.DELETE.getUrl(),
ModelAuthorizationAction.RECOMPUTE.getUrl(),
ModelAuthorizationAction.ASSIGN.getUrl(),
ModelAuthorizationAction.UNASSIGN.getUrl(),
ModelAuthorizationAction.DELEGATE.getUrl(),
ModelAuthorizationAction.CHANGE_CREDENTIALS.getUrl());
var result = parentResult.createSubresult(OP_AUTHORIZE_CHANGE_EXECUTION_START);
try {
// We do not need to check both phases here: normally, only REQUEST should be needed.
// (For example, the #assign operation is relevant for the EXECUTION phase.)
var phase = context.isExecutionPhaseOnly() ? AuthorizationPhaseType.EXECUTION : AuthorizationPhaseType.REQUEST;
if (!securityEnforcer.hasAnyAllowAuthorization(relevantActions, phase)) {
throw new SecurityViolationException("Not authorized to request execution of changes");
}
PartialProcessingOptionsType partialProcessing = ModelExecuteOptions.getPartialProcessing(options);
if (partialProcessing != null) {
// TODO Note that the information about the object may be incomplete (orgs, tenants, roles) or even missing.
// See MID-9454.
PrismObject<? extends ObjectType> object = context.getFocusContext().getObjectAny();
securityEnforcer.authorize(
ModelAuthorizationAction.PARTIAL_EXECUTION.getUrl(),
phase, AuthorizationParameters.Builder.buildObject(object), task, result);
}
} catch (Throwable t) {
result.recordException(t);
clockworkAuditHelper.auditRequestDenied(context, task, result, parentResult);
throw t;
} finally {
result.close();
}
}

Expand Down Expand Up @@ -501,6 +527,11 @@ public <F extends ObjectType> void executeRecompute(
LOGGER.debug("Recomputing {}", focus);
LensContext<F> lensContext = contextFactory.createRecomputeContext(focus, options, task, result);

securityEnforcer.authorize(
ModelAuthorizationAction.RECOMPUTE.getUrl(), AuthorizationPhaseType.REQUEST,
AuthorizationParameters.forObject(focus.asObjectable()),
SecurityEnforcer.Options.create(), task, result);

LOGGER.trace("Recomputing {}, context:\n{}", focus, lensContext.debugDumpLazily());
clockwork.run(lensContext, task, result);
}
Expand Down Expand Up @@ -1394,12 +1425,14 @@ public SearchResultMetadata searchReferencesIterative(

@Override
public OperationResult testResource(String resourceOid, Task task, OperationResult result)
throws ObjectNotFoundException, SchemaException, ConfigurationException {
throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException,
CommunicationException, SecurityViolationException {
Validate.notEmpty(resourceOid, "Resource oid must not be null or empty.");
LOGGER.trace("Testing resource OID: {}", resourceOid);

enterModelMethod();
try {
authorizeResourceOperation(ModelAuthorizationAction.TEST, resourceOid, task, result);
OperationResult testResult = provisioning.testResource(resourceOid, task, result);
LOGGER.debug("Finished testing resource OID: {}, result: {} ", resourceOid, testResult.getStatus());
LOGGER.trace("Test result:\n{}", lazy(() -> testResult.dump(false)));
Expand All @@ -1413,6 +1446,21 @@ public OperationResult testResource(String resourceOid, Task task, OperationResu
}
}

private ResourceType authorizeResourceOperation(
@NotNull ModelAuthorizationAction action, @NotNull String resourceOid,
@NotNull Task task, @NotNull OperationResult result)
throws SchemaException, ExpressionEvaluationException, CommunicationException, SecurityViolationException,
ConfigurationException, ObjectNotFoundException {
// Retrieved in full in order to evaluate the authorization
var fullResource = provisioning.getObject(ResourceType.class, resourceOid, createReadOnlyCollection(), task, result);
securityEnforcer.authorize(
action.getUrl(),
null,
AuthorizationParameters.forObject(fullResource.asObjectable()),
task, result);
return fullResource.asObjectable();
}

@Override
public OperationResult testResource(PrismObject<ResourceType> resource, Task task, OperationResult result)
throws ObjectNotFoundException, SchemaException, ConfigurationException {
Expand Down Expand Up @@ -1522,10 +1570,8 @@ public void importFromResource(String resourceOid, QName objectClass, Task task,
result.addArbitraryObjectAsParam(OperationResult.PARAM_TASK, task);
// TODO: add context to the result

// Fetch resource definition from the repo/provisioning
ResourceType resource;
try {
resource = getObject(ResourceType.class, resourceOid, createReadOnlyCollection(), task, result).asObjectable();
var resource = authorizeResourceOperation(ModelAuthorizationAction.IMPORT_FROM_RESOURCE, resourceOid, task, result);

// Here was a check on synchronization configuration, providing a warning if there is no configuration set up.
// But with changes in 4.6 it is not so easy to definitely tell that there's no synchronization set up,
Expand Down Expand Up @@ -1565,7 +1611,11 @@ public void importFromResource(String shadowOid, Task task, OperationResult pare
// TODO: add context to the result

try {
// FIXME autz?
// fetching the shadow just to get the resource OID (for the authorization)
var shadow = cacheRepositoryService.getObject(ShadowType.class, shadowOid, null, result);
var resourceOid = ShadowUtil.getResourceOidRequired(shadow.asObjectable());
authorizeResourceOperation(ModelAuthorizationAction.IMPORT_FROM_RESOURCE, resourceOid, task, result);

importFromResourceLauncher.importSingleShadow(shadowOid, task, result);
} catch (Throwable t) {
ModelImplUtils.recordException(result, t);
Expand Down Expand Up @@ -2583,7 +2633,8 @@ public void notifyChange(ResourceObjectShadowChangeDescriptionType changeDescrip
PrismObject<ShadowType> resourceObject = getResourceObject(changeDescription);
ObjectDelta<ShadowType> objectDelta = getObjectDelta(changeDescription, result);

// FIXME autz
securityEnforcer.authorize(ModelAuthorizationAction.NOTIFY_CHANGE.getUrl(), task, result);

ExternalResourceEvent event = new ExternalResourceEvent(objectDelta, resourceObject,
oldRepoShadow, changeDescription.getChannel());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1299,7 +1299,8 @@ public <T extends ObjectType> int countObjects(Class<T> type,

@Override
public OperationResult testResource(String resourceOid)
throws ObjectNotFoundException, SchemaException, ConfigurationException {
throws ObjectNotFoundException, SchemaException, ConfigurationException, SecurityViolationException,
ExpressionEvaluationException, CommunicationException {
return modelService.testResource(resourceOid, getCurrentTask(), getCurrentResult("testResource"));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
import java.util.Collection;
import javax.xml.datatype.XMLGregorianCalendar;

import com.evolveum.midpoint.task.api.ExpressionEnvironment;
import com.evolveum.midpoint.task.api.ExpressionEnvironmentSupplier;

import com.google.common.base.Preconditions;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
Expand Down Expand Up @@ -244,4 +242,27 @@ private void checkNamesArePresent(Collection<ObjectDeltaOperation<? extends Obje
}
}
}

/**
* The request was denied before its execution was even started.
* This is a special case.
*
* @param opResult The (closed) result of the operation that failed
* @param execResult The (open) result under which the auditing should be done
*/
public void auditRequestDenied(
@NotNull LensContext<? extends ObjectType> context,
@NotNull Task task,
@NotNull OperationResult opResult,
@NotNull OperationResult execResult) {
Preconditions.checkArgument(opResult.isClosed());
Preconditions.checkArgument(!execResult.isClosed());
// We do not care much about the event type here. At least not now.
AuditEventRecord auditRecord = new AuditEventRecord(AuditEventType.MODIFY_OBJECT, AuditEventStage.REQUEST);
auditRecord.setRequestIdentifier(context.getRequestIdentifier());
auditRecord.setChannel(context.getChannel());
auditRecord.setOutcome(opResult.getStatus());
auditRecord.setMessage(opResult.getMessage());
auditHelper.audit(auditRecord, context.getNameResolver(), task, execResult);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ public void setDoReconciliationForAllProjections(boolean doReconciliationForAllP
this.doReconciliationForAllProjections = doReconciliationForAllProjections;
}

boolean isExecutionPhaseOnly() {
public boolean isExecutionPhaseOnly() {
return executionPhaseOnly;
}

Expand Down

0 comments on commit b83016c

Please sign in to comment.