Skip to content

Commit

Permalink
Persona operation authorizations
Browse files Browse the repository at this point in the history
  • Loading branch information
semancik committed May 5, 2017
1 parent 62895cb commit b2db33b
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 55 deletions.
Expand Up @@ -100,6 +100,16 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="executionPhaseOnly" type="xsd:boolean" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
If set to true then all operations are considered to be
in execution phase - for the purpose of authorizations and auditing.
This is used in case that the whole operation (context) is a
secondary change, e.g. in case that persona is provisioned.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="projectionWave" type="xsd:int" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Expand Down
Expand Up @@ -128,7 +128,7 @@ public class ModelController implements ModelService, TaskService, WorkflowServi
private Clockwork clockwork;

@Autowired(required = true)
PrismContext prismContext;
private PrismContext prismContext;

@Autowired(required = true)
private ProvisioningService provisioning;
Expand Down Expand Up @@ -174,16 +174,16 @@ public class ModelController implements ModelService, TaskService, WorkflowServi
private UserProfileService userProfileService;

@Autowired(required = true)
Projector projector;
private Projector projector;

@Autowired(required = true)
Protector protector;
private Protector protector;

@Autowired(required = true)
ModelDiagController modelDiagController;
private ModelDiagController modelDiagController;

@Autowired(required = true)
ContextFactory contextFactory;
private ContextFactory contextFactory;

@Autowired(required = true)
private SchemaTransformer schemaTransformer;
Expand Down
Expand Up @@ -1175,13 +1175,13 @@ private <F extends ObjectType, O extends ObjectType> ObjectSecurityConstraints a
ContainerDelta<Containerable> assignmentDelta = primaryDelta.findContainerDelta(FocusType.F_ASSIGNMENT);
if (assignmentDelta != null) {
AuthorizationDecisionType assignmentItemDecision = securityConstraints.findItemDecision(new ItemPath(FocusType.F_ASSIGNMENT),
operationUrl, AuthorizationPhaseType.REQUEST);
operationUrl, getRequestAuthorizationPhase(context));
if (assignmentItemDecision == AuthorizationDecisionType.ALLOW) {
// Nothing to do, operation is allowed for all values
} else if (assignmentItemDecision == AuthorizationDecisionType.DENY) {
throw new AuthorizationException("Access denied");
} else {
AuthorizationDecisionType actionDecision = securityConstraints.getActionDecision(operationUrl, AuthorizationPhaseType.REQUEST);
AuthorizationDecisionType actionDecision = securityConstraints.getActionDecision(operationUrl, getRequestAuthorizationPhase(context));
if (actionDecision == AuthorizationDecisionType.ALLOW) {
// Nothing to do, operation is allowed for all values
} else if (actionDecision == AuthorizationDecisionType.DENY) {
Expand All @@ -1191,12 +1191,12 @@ private <F extends ObjectType, O extends ObjectType> ObjectSecurityConstraints a
// process each assignment individually
DeltaSetTriple<EvaluatedAssignmentImpl<?>> evaluatedAssignmentTriple = context.getEvaluatedAssignmentTriple();

authorizeAssignmentRequest(ModelAuthorizationAction.ASSIGN.getUrl(),
authorizeAssignmentRequest(context, ModelAuthorizationAction.ASSIGN.getUrl(),
object, ownerResolver, evaluatedAssignmentTriple.getPlusSet(), true, result);

// We want to allow unassignment even if there are policies. Otherwise we would not be able to get
// rid of that assignment
authorizeAssignmentRequest(ModelAuthorizationAction.UNASSIGN.getUrl(),
authorizeAssignmentRequest(context, ModelAuthorizationAction.UNASSIGN.getUrl(),
object, ownerResolver, evaluatedAssignmentTriple.getMinusSet(), false, result);
}
}
Expand All @@ -1221,7 +1221,7 @@ private <F extends ObjectType, O extends ObjectType> ObjectSecurityConstraints a
for (Item<?,?> item: credentialsContainer.getValue().getItems()) {
ContainerDelta<?> cdelta = new ContainerDelta(item.getPath(), (PrismContainerDefinition)item.getDefinition(), prismContext);
cdelta.addValuesToAdd(((PrismContainer)item).getValue().clone());
AuthorizationDecisionType cdecision = evaluateCredentialDecision(securityConstraints, cdelta);
AuthorizationDecisionType cdecision = evaluateCredentialDecision(context, securityConstraints, cdelta);
LOGGER.trace("AUTZ: credential add {} decision: {}", item.getPath(), cdecision);
if (cdecision == AuthorizationDecisionType.ALLOW) {
// Remove it from primary delta, so it will not be evaluated later
Expand All @@ -1237,7 +1237,7 @@ private <F extends ObjectType, O extends ObjectType> ObjectSecurityConstraints a
// modify
Collection<? extends ItemDelta<?, ?>> credentialChanges = primaryDelta.findItemDeltasSubPath(new ItemPath(UserType.F_CREDENTIALS));
for (ItemDelta credentialChange: credentialChanges) {
AuthorizationDecisionType cdecision = evaluateCredentialDecision(securityConstraints, credentialChange);
AuthorizationDecisionType cdecision = evaluateCredentialDecision(context, securityConstraints, credentialChange);
LOGGER.trace("AUTZ: credential delta {} decision: {}", credentialChange.getPath(), cdecision);
if (cdecision == AuthorizationDecisionType.ALLOW) {
// Remove it from primary delta, so it will not be evaluated later
Expand All @@ -1253,7 +1253,7 @@ private <F extends ObjectType, O extends ObjectType> ObjectSecurityConstraints a

if (primaryDelta != null && !primaryDelta.isEmpty()) {
// TODO: optimize, avoid evaluating the constraints twice
securityEnforcer.authorize(operationUrl, AuthorizationPhaseType.REQUEST, object, primaryDelta, null, ownerResolver, result);
securityEnforcer.authorize(operationUrl, getRequestAuthorizationPhase(context) , object, primaryDelta, null, ownerResolver, result);
}

return securityConstraints;
Expand All @@ -1262,12 +1262,20 @@ private <F extends ObjectType, O extends ObjectType> ObjectSecurityConstraints a
}
}

private AuthorizationDecisionType evaluateCredentialDecision(ObjectSecurityConstraints securityConstraints, ItemDelta credentialChange) {
private <F extends ObjectType> AuthorizationPhaseType getRequestAuthorizationPhase(LensContext<F> context) {
if (context.isExecutionPhaseOnly()) {
return AuthorizationPhaseType.EXECUTION;
} else {
return AuthorizationPhaseType.REQUEST;
}
}

private <F extends ObjectType> AuthorizationDecisionType evaluateCredentialDecision(LensContext<F> context, ObjectSecurityConstraints securityConstraints, ItemDelta credentialChange) {
return securityConstraints.findItemDecision(credentialChange.getPath(),
ModelAuthorizationAction.CHANGE_CREDENTIALS.getUrl(), AuthorizationPhaseType.REQUEST);
ModelAuthorizationAction.CHANGE_CREDENTIALS.getUrl(), getRequestAuthorizationPhase(context));
}

private <F extends FocusType,O extends ObjectType> void authorizeAssignmentRequest(String assignActionUrl, PrismObject<O> object,
private <F extends ObjectType,O extends ObjectType> void authorizeAssignmentRequest(LensContext<F> context, String assignActionUrl, PrismObject<O> object,
OwnerResolver ownerResolver, Collection<EvaluatedAssignmentImpl<?>> evaluatedAssignments, boolean prohibitPolicies, OperationResult result) throws SecurityViolationException, SchemaException {
if (evaluatedAssignments == null) {
return;
Expand All @@ -1277,25 +1285,25 @@ private <F extends FocusType,O extends ObjectType> void authorizeAssignmentReque
if (prohibitPolicies) {
AssignmentType assignmentType = evaluatedAssignment.getAssignmentType();
if (assignmentType.getPolicyRule() != null || !assignmentType.getPolicyException().isEmpty() || !assignmentType.getPolicySituation().isEmpty()) {
securityEnforcer.failAuthorization("with assignment because of policies in the assignment", AuthorizationPhaseType.REQUEST, object, null, target, result);
securityEnforcer.failAuthorization("with assignment because of policies in the assignment", getRequestAuthorizationPhase(context), object, null, target, result);
}
}
ObjectDelta<O> assignmentObjectDelta = object.createModifyDelta();
ContainerDelta<AssignmentType> assignmentDelta = assignmentObjectDelta.createContainerModification(FocusType.F_ASSIGNMENT);
// We do not care if this is add or delete. All that matters for authorization is that it is in a delta.
assignmentDelta.addValuesToAdd(evaluatedAssignment.getAssignmentType().asPrismContainerValue().clone());
if (securityEnforcer.isAuthorized(assignActionUrl, AuthorizationPhaseType.REQUEST, object, assignmentObjectDelta, target, ownerResolver)) {
if (securityEnforcer.isAuthorized(assignActionUrl, getRequestAuthorizationPhase(context), object, assignmentObjectDelta, target, ownerResolver)) {
LOGGER.trace("Operation authorized with {} authorization", assignActionUrl);
continue;
}
QName relation = evaluatedAssignment.getRelation();
if (ObjectTypeUtil.isDelegationRelation(relation)) {
if (securityEnforcer.isAuthorized(ModelAuthorizationAction.DELEGATE.getUrl(), AuthorizationPhaseType.REQUEST, object, assignmentObjectDelta, target, ownerResolver)) {
if (securityEnforcer.isAuthorized(ModelAuthorizationAction.DELEGATE.getUrl(), getRequestAuthorizationPhase(context), object, assignmentObjectDelta, target, ownerResolver)) {
LOGGER.trace("Operation authorized with {} authorization", ModelAuthorizationAction.DELEGATE.getUrl());
continue;
}
}
securityEnforcer.failAuthorization("with assignment", AuthorizationPhaseType.REQUEST, object, null, target, result);
securityEnforcer.failAuthorization("with assignment", getRequestAuthorizationPhase(context), object, null, target, result);
}
}

Expand Down
Expand Up @@ -109,6 +109,14 @@ public class LensContext<F extends ObjectType> implements ModelContext<F> {
* True if we want to reconcile all accounts in this context.
*/
private boolean doReconciliationForAllProjections = false;

/**
* If set to true then all operations are considered to be
* in execution phase - for the purpose of authorizations and auditing.
* This is used in case that the whole operation (context) is a
* secondary change, e.g. in case that persona is provisioned.
*/
private boolean executionPhaseOnly = false;

/**
* Current wave of computation and execution.
Expand Down Expand Up @@ -430,6 +438,14 @@ public boolean isReconcileFocus() {
return doReconciliationForAllProjections || ModelExecuteOptions.isReconcileFocus(options);
}

public boolean isExecutionPhaseOnly() {
return executionPhaseOnly;
}

public void setExecutionPhaseOnly(boolean executionPhaseOnly) {
this.executionPhaseOnly = executionPhaseOnly;
}

public DeltaSetTriple<EvaluatedAssignmentImpl<?>> getEvaluatedAssignmentTriple() {
return evaluatedAssignmentTriple;
}
Expand Down Expand Up @@ -785,6 +801,7 @@ protected void copyValues(LensContext<F> clone) {
clone.state = this.state;
clone.channel = this.channel;
clone.doReconciliationForAllProjections = this.doReconciliationForAllProjections;
clone.executionPhaseOnly = this.executionPhaseOnly;
clone.focusClass = this.focusClass;
clone.isFresh = this.isFresh;
clone.prismContext = this.prismContext;
Expand Down Expand Up @@ -865,6 +882,9 @@ public String debugDump(int indent, boolean showTriples) {
if (systemConfiguration == null) {
sb.append(" null-system-configuration");
}
if (executionPhaseOnly) {
sb.append(" execution-phase-only");
}
sb.append("\n");

DebugUtil.debugDumpLabel(sb, "Channel", indent + 1);
Expand Down Expand Up @@ -1051,6 +1071,7 @@ public PrismContainer<LensContextType> toPrismContainer() throws SchemaException
}
lensContextType.setFocusClass(focusClass != null ? focusClass.getName() : null);
lensContextType.setDoReconciliationForAllProjections(doReconciliationForAllProjections);
lensContextType.setExecutionPhaseOnly(executionPhaseOnly);
lensContextType.setProjectionWave(projectionWave);
lensContextType.setExecutionWave(executionWave);
lensContextType.setOptions(options != null ? options.toModelExecutionOptionsType() : null);
Expand Down Expand Up @@ -1100,6 +1121,9 @@ public static LensContext fromLensContextType(LensContextType lensContextType, P
lensContext.setDoReconciliationForAllProjections(
lensContextType.isDoReconciliationForAllProjections() != null
? lensContextType.isDoReconciliationForAllProjections() : false);
lensContext.setExecutionPhaseOnly(
lensContextType.isExecutionPhaseOnly() != null
? lensContextType.isExecutionPhaseOnly() : false);
lensContext.setProjectionWave(
lensContextType.getProjectionWave() != null ? lensContextType.getProjectionWave() : 0);
lensContext.setExecutionWave(
Expand Down
Expand Up @@ -91,13 +91,16 @@ public class PersonaProcessor {
@Autowired(required=true)
private ObjectResolver objectResolver;

@Autowired(required=true)
private ModelService modelService;

@Autowired
@Qualifier("cacheRepositoryService")
private transient RepositoryService repositoryService;

@Autowired(required = true)
private ContextFactory contextFactory;

@Autowired(required = true)
private Clockwork clockwork;

@Autowired(required=true)
private Clock clock;

Expand Down Expand Up @@ -295,7 +298,7 @@ public <F extends FocusType, T extends FocusType> void personaAdd(LensContext<F>

LOGGER.trace("Creating persona:\n{}", target.debugDumpLazily());

modelService.executeChanges(MiscSchemaUtil.createCollection(targetDelta), null, task, result);
executePersonaDelta(targetDelta, task, result);

link(context, target.asObjectable(), result);
}
Expand Down Expand Up @@ -325,14 +328,15 @@ public <F extends FocusType, T extends FocusType> void personaModify(LensContext
targetDelta.addModification(itemDelta);
}

modelService.executeChanges(MiscSchemaUtil.createCollection(targetDelta), null, task, result);
executePersonaDelta(targetDelta, task, result);
}

public <F extends FocusType> void personaDelete(LensContext<F> context, PersonaKey key, FocusType existingPersona, Task task, OperationResult result) throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, PolicyViolationException, SecurityViolationException {
PrismObject<F> focus = context.getFocusContext().getObjectOld();
LOGGER.debug("Deleting persona {} for {}: ", key, focus, existingPersona);
ObjectDelta<? extends FocusType> delta = existingPersona.asPrismObject().createDeleteDelta();
modelService.executeChanges(MiscSchemaUtil.createCollection(delta), null, task, result);
ObjectDelta<? extends FocusType> targetDelta = existingPersona.asPrismObject().createDeleteDelta();

executePersonaDelta(targetDelta, task, result);

unlink(context, existingPersona, result);
}
Expand All @@ -356,6 +360,15 @@ private <F extends FocusType> void unlink(LensContext<F> context, FocusType per

repositoryService.modifyObject(delta.getObjectTypeClass(), delta.getOid(), delta.getModifications(), result);
}

private <O extends ObjectType> void executePersonaDelta(ObjectDelta<O> delta, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, SecurityViolationException {
Collection<ObjectDelta<? extends ObjectType>> deltas = MiscSchemaUtil.createCollection(delta);
LensContext<? extends ObjectType> context = contextFactory.createContext(deltas, null, task, result);
// Persona changes are all "secondary" changes, trigerred by roles and policies. We do not want to authorize
// them as REQUEST. Assignment of the persona role was REQUEST. Changes in persona itself is all EXECUTION.
context.setExecutionPhaseOnly(true);
clockwork.run(context, task, result);
}

class PersonaKey implements HumanReadableDescribable {

Expand Down
Expand Up @@ -96,7 +96,10 @@ public class AbstractConfiguredModelIntegrationTest extends AbstractModelIntegra

protected static final String USER_TEMPLATE_ORG_ASSIGNMENT_FILENAME = COMMON_DIR + "/user-template-org-assignment.xml";
protected static final String USER_TEMPLATE_ORG_ASSIGNMENT_OID = "10000000-0000-0000-0000-000000000444";


protected static final File OBJECT_TEMPLATE_PERSONA_ADMIN_FILE = new File(COMMON_DIR, "object-template-persona-admin.xml");
protected static final String OBJECT_TEMPLATE_PERSONA_ADMIN_OID = "894ea1a8-2c0a-11e7-a950-ff2047b0c053";

protected static final String CONNECTOR_LDAP_FILENAME = COMMON_DIR + "/connector-ldap.xml";

protected static final String CONNECTOR_DBTABLE_FILENAME = COMMON_DIR + "/connector-dbtable.xml";
Expand Down Expand Up @@ -255,6 +258,9 @@ public class AbstractConfiguredModelIntegrationTest extends AbstractModelIntegra

protected static final File ROLE_DRINKER_FILE = new File(COMMON_DIR, "role-drinker.xml");
protected static final String ROLE_DRINKER_OID = "0abbde4c-ab3f-11e6-910d-d7dabf5f09f0";

protected static final File ROLE_PERSONA_ADMIN_FILE = new File(COMMON_DIR, "role-persona-admin.xml");
protected static final String ROLE_PERSONA_ADMIN_OID = "16813ae6-2c0a-11e7-91fc-8333c244329e";

protected static final File USER_JACK_FILE = new File(COMMON_DIR, "user-jack.xml");
protected static final String USER_JACK_OID = "c0c010c0-d34d-b33f-f00d-111111111111";
Expand Down
Expand Up @@ -107,12 +107,6 @@
public class TestPersona extends AbstractInitializedModelIntegrationTest {

public static final File TEST_DIR = new File("src/test/resources/persona");

protected static final File OBJECT_TEMPLATE_PERSONA_ADMIN_FILE = new File(TEST_DIR, "object-template-persona-admin.xml");
protected static final String OBJECT_TEMPLATE_PERSONA_ADMIN_OID = "894ea1a8-2c0a-11e7-a950-ff2047b0c053";

protected static final File ROLE_PERSONA_ADMIN_FILE = new File(TEST_DIR, "role-persona-admin.xml");
protected static final String ROLE_PERSONA_ADMIN_OID = "16813ae6-2c0a-11e7-91fc-8333c244329e";

private static final String USER_JACK_GIVEN_NAME_NEW = "Jackie";

Expand Down

0 comments on commit b2db33b

Please sign in to comment.