Skip to content

Commit

Permalink
Store deltas and bulk actions encrypted (MID-5359)
Browse files Browse the repository at this point in the history
Also refactored CryptoUtil to be more generic and maintainable.
Fixed executeChangesAsynchronously to return task with new OID.

(cherry-picked from 840bb43f5a89ad3ec02726db6ae3f1b60f411a80)
  • Loading branch information
mederly committed May 22, 2019
1 parent 073e8dd commit 6dd5aa5
Show file tree
Hide file tree
Showing 9 changed files with 1,163 additions and 134 deletions.

Large diffs are not rendered by default.

@@ -0,0 +1,64 @@
<!--
~ Copyright (c) 2010-2019 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.
-->

<task xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3"
xmlns:c="http://midpoint.evolveum.com/xml/ns/public/common/common-3"
xmlns:org="http://midpoint.evolveum.com/xml/ns/public/common/org-3"
xmlns:q="http://prism.evolveum.com/xml/ns/public/query-3"
xmlns:t="http://prism.evolveum.com/xml/ns/public/types-3" oid="9de76345-0f02-48de-86bf-e7a887cb374a">
<name>Task 1555581798624-0-1</name>
<extension xmlns:se="http://midpoint.evolveum.com/xml/ns/public/model/scripting/extension-3">
<se:executeScript xmlns:s="http://midpoint.evolveum.com/xml/ns/public/model/scripting-3">
<s:pipeline list="true">
<s:search>
<s:type>c:UserType</s:type>
<s:searchFilter>
<q:equal>
<q:path>c:name</q:path>
<q:value>jack</q:value>
</q:equal>
</s:searchFilter>
</s:search>
<s:action>
<s:type>modify</s:type>
<s:parameter>
<s:name>delta</s:name>
<value xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="t:ObjectDeltaType">
<t:changeType>modify</t:changeType>
<t:objectType>UserType</t:objectType>
<t:itemDelta>
<t:modificationType>replace</t:modificationType>
<t:path>credentials/password/value</t:path>
<t:value xsi:type="t:ProtectedStringType">
<t:clearValue>pass1234word</t:clearValue>
</t:value>
</t:itemDelta>
</value>
</s:parameter>
</s:action>
</s:pipeline>
</se:executeScript>
</extension>
<taskIdentifier>1555581798624-0-1</taskIdentifier>
<ownerRef oid="00000000-0000-0000-0000-000000000002" relation="org:default" type="c:UserType">
<!-- administrator -->
</ownerRef>
<executionStatus>runnable</executionStatus>
<category>BulkActions</category>
<handlerUri>http://midpoint.evolveum.com/xml/ns/public/model/scripting/handler-3</handlerUri>
<recurrence>single</recurrence>
<binding>tight</binding>
</task>
Expand Up @@ -2,6 +2,8 @@

import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.xml.XsdTypeMapper;
import com.evolveum.midpoint.prism.xnode.PrimitiveXNode;
import com.evolveum.midpoint.prism.xnode.RootXNode;
import com.evolveum.midpoint.prism.xnode.XNode;
Expand Down Expand Up @@ -77,6 +79,70 @@ public RawType(PrismValue parsed, QName explicitTypeName, @NotNull PrismContext
}
}

/**
* Extracts a "real value" from a potential RawType object without expecting any specific type beforehand.
* (Useful e.g. for determining value of xsd:anyType XML property.)
*/
public static Object getValue(Object value) throws SchemaException {
if (value instanceof RawType) {
return ((RawType) value).getValue();
} else {
return value;
}
}

public Object getValue() throws SchemaException {
return getValue(false);
}

/**
* Extracts a "real value" from RawType object without expecting any specific type beforehand.
* If no explicit type is present, assumes xsd:string (and fails if the content is structured).
*/
public Object getValue(boolean store) throws SchemaException {
if (parsed != null) {
return parsed.getRealValue();
}
if (xnode == null) {
return null;
}
if (xnode.getTypeQName() != null) {
TypeDefinition typeDefinition = prismContext.getSchemaRegistry().findTypeDefinitionByType(xnode.getTypeQName());
if (typeDefinition != null && typeDefinition.getCompileTimeClass() != null) {
return storeIfRequested(getParsedRealValue(typeDefinition.getCompileTimeClass()), store);
}
Class<?> javaClass = XsdTypeMapper.getXsdToJavaMapping(xnode.getTypeQName());
if (javaClass != null) {
return storeIfRequested(getParsedRealValue(javaClass), store);
}
}
// unknown or null type -- try parsing as string
if (!(xnode instanceof PrimitiveXNode<?>)) {
throw new SchemaException("Trying to parse non-primitive XNode as type '" + xnode.getTypeQName() + "'");
} else {
return ((PrimitiveXNode) xnode).getStringValue();
}
}

private Object storeIfRequested(Object parsedValue, boolean store) {
if (parsed == null && store) {
if (parsedValue instanceof Containerable) {
parsed = ((Containerable) parsedValue).asPrismContainerValue();
xnode = null;
} else if (parsedValue instanceof Referencable) {
parsed = ((Referencable) parsedValue).asReferenceValue();
xnode = null;
} else if (parsedValue instanceof PolyStringType) {
parsed = new PrismPropertyValue<>(PolyString.toPolyString((PolyStringType) parsedValue)); // hack
xnode = null;
} else if (parsedValue != null) {
parsed = new PrismPropertyValue<>(parsedValue);
xnode = null;
}
}
return parsedValue;
}

@Override
public void revive(PrismContext prismContext) throws SchemaException {
Validate.notNull(prismContext);
Expand Down
Expand Up @@ -1612,8 +1612,9 @@ public TaskType executeChangesAsynchronously(Collection<ObjectDelta<?>> deltas,
newTask.asPrismObject().addExtensionItem(optionsProperty);
}
ObjectDelta<TaskType> taskAddDelta = ObjectDelta.createAddDelta(newTask.asPrismObject());
modelService.executeChanges(singleton(taskAddDelta), null, opTask, result);
return newTask;
Collection<ObjectDeltaOperation<? extends ObjectType>> operations = modelService
.executeChanges(singleton(taskAddDelta), null, opTask, result);
return (TaskType) operations.iterator().next().getObjectDelta().getObjectToAdd().asObjectable();
}

@Override
Expand Down
Expand Up @@ -29,6 +29,7 @@
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.util.PrismAsserts;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.internals.InternalMonitor;
Expand All @@ -39,6 +40,7 @@
import com.evolveum.midpoint.test.util.LogfileTestTailer;
import com.evolveum.midpoint.test.util.MidPointAsserts;
import com.evolveum.midpoint.test.util.TestUtil;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.midpoint.xml.ns._public.model.scripting_3.*;
Expand All @@ -53,6 +55,7 @@
import javax.xml.bind.JAXBException;
import javax.xml.namespace.QName;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
Expand All @@ -73,6 +76,9 @@
public class TestScriptingBasic extends AbstractInitializedModelIntegrationTest {

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

public static final File SYSTEM_CONFIGURATION_FILE = new File(TEST_DIR, "system-configuration.xml");

private static final String DOT_CLASS = TestScriptingBasic.class.getName() + ".";
private static final File LOG_FILE = new File(TEST_DIR, "log.xml");
private static final File SEARCH_FOR_USERS_FILE = new File(TEST_DIR, "search-for-users.xml");
Expand All @@ -90,6 +96,9 @@ public class TestScriptingBasic extends AbstractInitializedModelIntegrationTest
private static final File DELETE_AND_ADD_JACK_FILE = new File(TEST_DIR, "delete-and-add-jack.xml");
private static final File MODIFY_JACK_FILE = new File(TEST_DIR, "modify-jack.xml");
private static final File MODIFY_JACK_BACK_FILE = new File(TEST_DIR, "modify-jack-back.xml");
private static final File MODIFY_JACK_PASSWORD_FILE = new File(TEST_DIR, "modify-jack-password.xml");
private static final File MODIFY_JACK_PASSWORD_TASK_FILE = new File(TEST_DIR, "modify-jack-password-task.xml");
private static final String MODIFY_JACK_PASSWORD_TASK_OID = "9de76345-0f02-48de-86bf-e7a887cb374a";
private static final File RECOMPUTE_JACK_FILE = new File(TEST_DIR, "recompute-jack.xml");
private static final File ASSIGN_TO_JACK_FILE = new File(TEST_DIR, "assign-to-jack.xml");
private static final File ASSIGN_TO_JACK_2_FILE = new File(TEST_DIR, "assign-to-jack-2.xml");
Expand Down Expand Up @@ -117,6 +126,11 @@ public class TestScriptingBasic extends AbstractInitializedModelIntegrationTest
private static final QName USER_DESCRIPTION_TASK_EXTENSION_PROPERTY = new QName("http://midpoint.evolveum.com/xml/ns/samples/piracy", "userDescription");
private static final QName STUDY_GROUP_TASK_EXTENSION_PROPERTY = new QName("http://midpoint.evolveum.com/xml/ns/samples/piracy", "studyGroup");

private static final String PASSWORD_PLAINTEXT_FRAGMENT = "pass1234wor";
private static final String PASSWORD_PLAINTEXT_1 = "pass1234wor1";
private static final String PASSWORD_PLAINTEXT_2 = "pass1234wor2";
private static final String PASSWORD_PLAINTEXT_3 = "pass1234wor3";

@Autowired
private ScriptingExpressionEvaluator scriptingExpressionEvaluator;

Expand All @@ -128,9 +142,16 @@ public void initSystem(Task initTask, OperationResult initResult)

// InternalMonitor.setTraceShadowFetchOperation(true);
// InternalMonitor.setTraceResourceSchemaOperations(true);

DebugUtil.setPrettyPrintBeansAs(PrismContext.LANG_YAML);
}

@Test
@Override
protected File getSystemConfigurationFile() {
return SYSTEM_CONFIGURATION_FILE;
}

@Test
public void test100EmptySequence() throws Exception {
final String TEST_NAME = "test100EmptySequence";
TestUtil.displayTestTitle(this, TEST_NAME);
Expand Down Expand Up @@ -1192,6 +1213,124 @@ public void test570ResumeTask() throws Exception {
assertTrue("Task is still suspended", taskAfter.asObjectable().getExecutionStatus() != TaskExecutionStatusType.SUSPENDED);
}

// MID-5359
@Test
public void test600ModifyJackPasswordInBackground() throws Exception {
final String TEST_NAME = "test600ModifyJackPasswordInBackground";
TestUtil.displayTestTitle(this, TEST_NAME);

// GIVEN
OperationResult result = new OperationResult(DOT_CLASS + TEST_NAME);
PrismProperty<ScriptingExpressionType> expression = parseAnyData(MODIFY_JACK_PASSWORD_FILE);

prepareNotifications();
dummyAuditService.clear();

// WHEN
Task task = taskManager.createTaskInstance();
task.setOwner(getUser(USER_ADMINISTRATOR_OID));
scriptingExpressionEvaluator.evaluateExpressionInBackground(expression.getAnyValue().getValue(), task, result);
waitForTaskFinish(task.getOid(), false);
task.refresh(result);

// THEN
display(task.getResult());
TestUtil.assertSuccess(task.getResult());
PrismObject<UserType> jack = getUser(USER_JACK_OID);
display("jack after password change", jack);
assertEncryptedUserPassword(jack, PASSWORD_PLAINTEXT_1);

String xml = prismContext.xmlSerializer().serialize(task.getTaskPrismObject());
display("task", xml);
assertFalse("Plaintext password is present in the task", xml.contains(PASSWORD_PLAINTEXT_FRAGMENT));

display("Dummy transport", dummyTransport);
display("Audit", dummyAuditService);
}

// MID-5359
@Test
public void test610ModifyJackPasswordImportingTask() throws Exception {
final String TEST_NAME = "test610ModifyJackPasswordImportingTask";
TestUtil.displayTestTitle(this, TEST_NAME);

// GIVEN
Task opTask = taskManager.createTaskInstance(DOT_CLASS + TEST_NAME);
opTask.setOwner(getUser(USER_ADMINISTRATOR_OID));
OperationResult result = opTask.getResult();

prepareNotifications();
dummyAuditService.clear();

// WHEN
FileInputStream stream = new FileInputStream(MODIFY_JACK_PASSWORD_TASK_FILE);
modelService.importObjectsFromStream(stream, PrismContext.LANG_XML, null, opTask, result);
stream.close();

result.computeStatus();
assertSuccess(result);

Task task = waitForTaskFinish(MODIFY_JACK_PASSWORD_TASK_OID, false);

// THEN
display(task.getResult());
TestUtil.assertSuccess(task.getResult());
PrismObject<UserType> jack = getUser(USER_JACK_OID);
display("jack after password change", jack);
assertEncryptedUserPassword(jack, PASSWORD_PLAINTEXT_2);

String xml = prismContext.xmlSerializer().serialize(task.getTaskPrismObject());
display("task", xml);
assertFalse("Plaintext password is present in the task", xml.contains(PASSWORD_PLAINTEXT_FRAGMENT));

display("Dummy transport", dummyTransport);
display("Audit", dummyAuditService);
}

// not using scripting as such, but related... MID-5359
@Test
public void test620ModifyJackPasswordViaExecuteChangesAsynchronously() throws Exception {
final String TEST_NAME = "test620ModifyJackPasswordViaExecuteChangesAsynchronously";
TestUtil.displayTestTitle(this, TEST_NAME);

// GIVEN
Task opTask = taskManager.createTaskInstance(DOT_CLASS + TEST_NAME);
opTask.setOwner(getUser(USER_ADMINISTRATOR_OID));
OperationResult result = opTask.getResult();

prepareNotifications();
dummyAuditService.clear();

// WHEN
ProtectedStringType password = new ProtectedStringType();
password.setClearValue(PASSWORD_PLAINTEXT_3);

ObjectDelta<UserType> delta = DeltaBuilder.deltaFor(UserType.class, prismContext)
.item(UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD, PasswordType.F_VALUE)
.replace(password)
.asObjectDeltaCast(USER_JACK_OID);
TaskType newTask = libraryMidpointFunctions.executeChangesAsynchronously(singleton(delta), null, null, opTask, result);

result.computeStatus();
assertSuccess(result);

Task task = waitForTaskFinish(newTask.getOid(), false);

// THEN
display(task.getResult());
TestUtil.assertSuccess(task.getResult());
PrismObject<UserType> jack = getUser(USER_JACK_OID);
display("jack after password change", jack);
assertEncryptedUserPassword(jack, PASSWORD_PLAINTEXT_3);

String xml = prismContext.xmlSerializer().serialize(task.getTaskPrismObject());
display("task", xml);
assertFalse("Plaintext password is present in the task", xml.contains(PASSWORD_PLAINTEXT_FRAGMENT));

display("Dummy transport", dummyTransport);
display("Audit", dummyAuditService);
}

private void assertNoOutputData(ExecutionContext output) {
assertTrue("Script returned unexpected data", output.getFinalOutput() == null || output.getFinalOutput().getData().isEmpty());
}
Expand Down

0 comments on commit 6dd5aa5

Please sign in to comment.