diff --git a/config/rest/index.html b/config/rest/index.html index e9ebc4c7fb5..80037e91c9a 100644 --- a/config/rest/index.html +++ b/config/rest/index.html @@ -18778,9 +18778,9 @@

Usage and SDK Samples

m_basic.setPassword("YOUR PASSWORD"); DefaultApi apiInstance = new DefaultApi(); - String type = type_example; // String | type of the object for which the value will be generated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services - String id = id_example; // String | oid of the object for which the value will be generated - Object body = ; // Object | path to the property which will be generated + String type = type_example; // String | type of the object for which the value will be validated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services + String id = id_example; // String | oid of the object for which the value will be validated + Object body = ; // Object | It can contain either path to the existing property which should be validated (e.g. policy for generating/validating employeeNumber changed and we need to check if the existing employeeNumber still satisfy constraints or it should be generated again) or value which will be set to the target object if it satisfy policy constraints. Definition of path to the existing property is used only if no value is provided to validate. try { Object result = apiInstance.validateValue(type, id, body); System.out.println(result); @@ -18803,9 +18803,9 @@

Usage and SDK Samples

public static void main(String[] args) { DefaultApi apiInstance = new DefaultApi(); - String type = type_example; // String | type of the object for which the value will be generated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services - String id = id_example; // String | oid of the object for which the value will be generated - Object body = ; // Object | path to the property which will be generated + String type = type_example; // String | type of the object for which the value will be validated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services + String id = id_example; // String | oid of the object for which the value will be validated + Object body = ; // Object | It can contain either path to the existing property which should be validated (e.g. policy for generating/validating employeeNumber changed and we need to check if the existing employeeNumber still satisfy constraints or it should be generated again) or value which will be set to the target object if it satisfy policy constraints. Definition of path to the existing property is used only if no value is provided to validate. try { Object result = apiInstance.validateValue(type, id, body); System.out.println(result); @@ -18833,9 +18833,9 @@

Usage and SDK Samples

[apiConfig setPassword:@"YOUR_PASSWORD"]; -String *type = type_example; // type of the object for which the value will be generated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services -String *id = id_example; // oid of the object for which the value will be generated -Object *body = ; // path to the property which will be generated +String *type = type_example; // type of the object for which the value will be validated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services +String *id = id_example; // oid of the object for which the value will be validated +Object *body = ; // It can contain either path to the existing property which should be validated (e.g. policy for generating/validating employeeNumber changed and we need to check if the existing employeeNumber still satisfy constraints or it should be generated again) or value which will be set to the target object if it satisfy policy constraints. Definition of path to the existing property is used only if no value is provided to validate. DefaultApi *apiInstance = [[DefaultApi alloc] init]; @@ -18866,11 +18866,11 @@

Usage and SDK Samples

var api = new .DefaultApi() -var type = type_example; // {String} type of the object for which the value will be generated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services +var type = type_example; // {String} type of the object for which the value will be validated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services -var id = id_example; // {String} oid of the object for which the value will be generated +var id = id_example; // {String} oid of the object for which the value will be validated -var body = ; // {Object} path to the property which will be generated +var body = ; // {Object} It can contain either path to the existing property which should be validated (e.g. policy for generating/validating employeeNumber changed and we need to check if the existing employeeNumber still satisfy constraints or it should be generated again) or value which will be set to the target object if it satisfy policy constraints. Definition of path to the existing property is used only if no value is provided to validate. var callback = function(error, data, response) { @@ -18909,9 +18909,9 @@

Usage and SDK Samples

Configuration.Default.Password = "YOUR_PASSWORD"; var apiInstance = new DefaultApi(); - var type = type_example; // String | type of the object for which the value will be generated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services - var id = id_example; // String | oid of the object for which the value will be generated - var body = new Object(); // Object | path to the property which will be generated + var type = type_example; // String | type of the object for which the value will be validated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services + var id = id_example; // String | oid of the object for which the value will be validated + var body = new Object(); // Object | It can contain either path to the existing property which should be validated (e.g. policy for generating/validating employeeNumber changed and we need to check if the existing employeeNumber still satisfy constraints or it should be generated again) or value which will be set to the target object if it satisfy policy constraints. Definition of path to the existing property is used only if no value is provided to validate. try { @@ -18941,9 +18941,9 @@

Usage and SDK Samples

io.swagger.client\Configuration::getDefaultConfiguration()->setPassword('YOUR_PASSWORD'); $api_instance = new io.swagger.client\Api\DefaultApi(); -$type = type_example; // String | type of the object for which the value will be generated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services -$id = id_example; // String | oid of the object for which the value will be generated -$body = ; // Object | path to the property which will be generated +$type = type_example; // String | type of the object for which the value will be validated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services +$id = id_example; // String | oid of the object for which the value will be validated +$body = ; // Object | It can contain either path to the existing property which should be validated (e.g. policy for generating/validating employeeNumber changed and we need to check if the existing employeeNumber still satisfy constraints or it should be generated again) or value which will be set to the target object if it satisfy policy constraints. Definition of path to the existing property is used only if no value is provided to validate. try { $result = $api_instance->validateValue($type, $id, $body); @@ -18981,7 +18981,7 @@

Parameters

var schemaWrapper = { "name" : "type", "in" : "path", - "description" : "type of the object for which the value will be generated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services", + "description" : "type of the object for which the value will be validated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services", "required" : true, "type" : "string" }; @@ -19014,7 +19014,7 @@

Parameters

var schemaWrapper = { "name" : "id", "in" : "path", - "description" : "oid of the object for which the value will be generated", + "description" : "oid of the object for which the value will be validated", "required" : true, "type" : "string" }; @@ -19058,7 +19058,7 @@

Parameters

var schemaWrapper = { "in" : "body", "name" : "body", - "description" : "path to the property which will be generated", + "description" : "It can contain either path to the existing property which should be validated (e.g. policy for generating/validating employeeNumber changed and we need to check if the existing employeeNumber still satisfy constraints or it should be generated again) or value which will be set to the target object if it satisfy policy constraints. Definition of path to the existing property is used only if no value is provided to validate.", "required" : true, "schema" : { "type" : "object", @@ -19784,7 +19784,7 @@

Status: 503 - Communication problem while provisioning

- Generated 2017-03-02T14:25:03.802+01:00 + Generated 2017-03-16T13:50:39.152+01:00
diff --git a/config/rest/swagger.yml b/config/rest/swagger.yml index c7d238046df..8ac4609aa6a 100644 --- a/config/rest/swagger.yml +++ b/config/rest/swagger.yml @@ -233,20 +233,20 @@ paths: - name: "type" in: "path" required: true - description: "type of the object for which the value will be generated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services" + description: "type of the object for which the value will be validated. Possible values are connectors, connectorHosts, genericObjects, resources, users, objectTemplates, systemConfigurations, tasks, shadows, roles, valuePolicies, orgs, services" type: "string" - name: "id" in: "path" required: true type: "string" - description: "oid of the object for which the value will be generated" + description: "oid of the object for which the value will be validated" - in: "body" name: "body" required: true schema: type: "object" description: "see PolicyItemsDefinitionType" - description: "path to the property which will be generated" + description: "It can contain either path to the existing property which should be validated (e.g. policy for generating/validating employeeNumber changed and we need to check if the existing employeeNumber still satisfy constraints or it should be generated again) or value which will be set to the target object if it satisfy policy constraints. Definition of path to the existing property is used only if no value is provided to validate." responses: 200: description: "Successful operation" diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageForgotPassword.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageForgotPassword.java index 0cedd4c4417..9e769f3be33 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageForgotPassword.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageForgotPassword.java @@ -30,7 +30,7 @@ import com.evolveum.midpoint.gui.api.page.PageBase; import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; -import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; import com.evolveum.midpoint.prism.Item; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismProperty; diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageSecurityQuestions.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageSecurityQuestions.java index 76e4bc32c12..c03ac4af35d 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageSecurityQuestions.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/forgetpassword/PageSecurityQuestions.java @@ -45,7 +45,7 @@ import com.evolveum.midpoint.gui.api.page.PageBase; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.model.api.ModelService; -import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismObjectDefinition; import com.evolveum.midpoint.prism.crypto.EncryptionException; diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/login/PageSelfRegistration.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/login/PageSelfRegistration.java index 43d68be4403..c0f1eaba8fa 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/login/PageSelfRegistration.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/login/PageSelfRegistration.java @@ -37,7 +37,7 @@ import com.evolveum.midpoint.gui.api.model.LoadableModel; import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismObjectDefinition; import com.evolveum.midpoint.prism.delta.ObjectDelta; diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/crypto/ProtectedData.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/crypto/ProtectedData.java index 2e40d9470e9..c2de3b4e22e 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/crypto/ProtectedData.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/crypto/ProtectedData.java @@ -24,15 +24,17 @@ */ public interface ProtectedData { - abstract byte[] getClearBytes(); + byte[] getClearBytes(); - abstract void setClearBytes(byte[] bytes); + void setClearBytes(byte[] bytes); - abstract T getClearValue(); + T getClearValue(); - abstract void setClearValue(T data); + void setClearValue(T data); - abstract void destroyCleartext(); + void destroyCleartext(); + + boolean canGetCleartext(); EncryptedDataType getEncryptedDataType(); diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java index c5899268aaa..df95ebcc088 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java @@ -1688,4 +1688,24 @@ public List getNewValuesFor(ItemPath itemPath) { } } } + + public List getDeletedValuesFor(ItemPath itemPath) { + if (isAdd()) { + Item item = objectToAdd.findItem(itemPath); + return item != null ? item.getValues() : Collections.emptyList(); + } else if (isDelete()) { + return Collections.emptyList(); + } else { + ItemDelta itemDelta = ItemDelta.findItemDelta(modifications, itemPath, ItemDelta.class); + if (itemDelta != null) { + if (itemDelta.getValuesToDelete() != null) { + return (List) itemDelta.getValuesToDelete(); + } else { + return Collections.emptyList(); + } + } else { + return Collections.emptyList(); + } + } + } } diff --git a/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/ProtectedDataType.java b/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/ProtectedDataType.java index b1d62f231f8..6f2375d7514 100644 --- a/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/ProtectedDataType.java +++ b/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/ProtectedDataType.java @@ -166,6 +166,11 @@ public T getClearValue() { public void setClearValue(T clearValue) { this.clearValue = clearValue; } + + @Override + public boolean canGetCleartext() { + return clearValue != null || encryptedDataType != null; + } @Override public void destroyCleartext() { diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java index 13355c6e1f4..b4c71e19350 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java @@ -65,6 +65,11 @@ public abstract class SchemaConstants { public static final String NS_MATCHING_RULE = NS_MIDPOINT_PUBLIC + "/common/matching-rule-3"; public static final String NS_FAULT = "http://midpoint.evolveum.com/xml/ns/public/common/fault-3"; public static final String NS_SAMPLES_EXTENSION = "http://midpoint.evolveum.com/xml/ns/samples/extension-3"; + + /** + * Namespace for default (bult-in) object collections, such as "all objects", "all roles", ... + */ + public static final String NS_OBJECT_COLLECTIONS = NS_MIDPOINT_PUBLIC + "/common/object-collections-3"; // COMMON NAMESPACE @@ -216,6 +221,7 @@ public abstract class SchemaConstants { public static final ItemPath PATH_CREDENTIALS_PASSWORD_FAILED_LOGINS = new ItemPath( UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD, PasswordType.F_FAILED_LOGINS); public static final ItemPath PATH_LINK_REF = new ItemPath(FocusType.F_LINK_REF); + public static final ItemPath PATH_LIFECYCLE_STATE = new ItemPath(ObjectType.F_LIFECYCLE_STATE); public static final String NS_PROVISIONING = NS_MIDPOINT_PUBLIC + "/provisioning"; public static final String NS_PROVISIONING_LIVE_SYNC = NS_PROVISIONING + "/liveSync-3"; @@ -398,6 +404,34 @@ public abstract class SchemaConstants { public static final String LIFECYCLE_FAILED = "failed"; + // Object collections + + /** + * All objects in role catalog. It means all the objects in all the categories that are placed under the + * primary role catalog defined in the system. If used in a context where the role catalog can be displayed + * as a tree then this collection will be displayed as a tree. + */ + public static final QName OBJECT_COLLECTION_ROLE_CATALOG_QNAME = new QName(NS_OBJECT_COLLECTIONS, "roleCatalog"); + public static final String OBJECT_COLLECTION_ROLE_CATALOG_URI = QNameUtil.qNameToUri(OBJECT_COLLECTION_ROLE_CATALOG_QNAME); + + /** + * Collection that contains all roles. + */ + public static final QName OBJECT_COLLECTION_ALL_ROLES_QNAME = new QName(NS_OBJECT_COLLECTIONS, "allRoles"); + public static final String OBJECT_COLLECTION_ALL_ROLES_URI = QNameUtil.qNameToUri(OBJECT_COLLECTION_ALL_ROLES_QNAME); + + /** + * Collection that contains all orgs. + */ + public static final QName OBJECT_COLLECTION_ALL_ORGS_QNAME = new QName(NS_OBJECT_COLLECTIONS, "allOrgs"); + public static final String OBJECT_COLLECTION_ALL_ORGS_URI = QNameUtil.qNameToUri(OBJECT_COLLECTION_ALL_ORGS_QNAME); + + /** + * Collection that contains all services. + */ + public static final QName OBJECT_COLLECTION_ALL_SERVICES_QNAME = new QName(NS_OBJECT_COLLECTIONS, "allServices"); + public static final String OBJECT_COLLECTION_ALL_SERVICES_URI = QNameUtil.qNameToUri(OBJECT_COLLECTION_ALL_SERVICES_QNAME); + // Samples public static final QName SAMPLES_SSN = new QName(SchemaConstants.NS_SAMPLES_EXTENSION, "ssn"); diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/FocusTypeUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/FocusTypeUtil.java index ae5171c3f4d..06d1703f9ce 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/FocusTypeUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/FocusTypeUtil.java @@ -23,11 +23,15 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentSelectorType; import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ConstructionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.OrderConstraintsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordType; import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; +import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; /** * @author semancik @@ -147,4 +151,19 @@ public static ShadowKindType determineConstructionKind(AssignmentType assignment throw new IllegalArgumentException("Construction not defined in the assigment."); } + + public static ProtectedStringType getPasswordValue(UserType user) { + if (user == null) { + return null; + } + CredentialsType creds = user.getCredentials(); + if (creds == null) { + return null; + } + PasswordType passwd = creds.getPassword(); + if (passwd == null) { + return null; + } + return passwd.getValue(); + } } diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd index 30ffdd89e99..5c46f5cc2de 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd @@ -4183,6 +4183,18 @@ + + + + Definition of resource object lifecycle handling. It defines + how the initial lifecycle state is determined, constraints for lifecycle + transitions, etc. + + + 3.6 + + + @@ -5078,6 +5090,39 @@ + + + + + Definition of resource object lifecycle handling. It defines + how the initial lifecycle state is determined, constraints for lifecycle + transitions, etc. + + + 3.6 + + + + + + + Lifecycle state mapping. This mapping influences how shadow lifecycle state and focus + lifecycle state are mapped. + Default outbound lifecycleState mapping is somehow smart algorithm that will consider + presence of the focus password, resource password capability, operation and so on. + It is defined to work well with hashed passwords. + Default inbound lifecycle mapping is empty: no mapping at all. In midPoint 3.6 the inbound + lifecycle mapping is not implemented. + + + + + + @@ -6255,6 +6300,21 @@ + + + + Reference to the user whose identity will be used to execute the expression. + The expression will use authorizations that this user has. If the expression + triggers any change then the change will be audited under the identity of + this user. + If no user is specified then the identify of currently logged-in user or + the owner of the task will be used. + + + tns:UserType + + + @@ -11072,6 +11132,19 @@ + + + + Specifies the set of object collections ("role catalog views") to display in role + catalog. If this element is not specified then the default (hardcoded) collections + will be displayed. If this element is specified then only those collections specified + in this element are displayed. + + + 3.6 + + + @@ -11137,6 +11210,61 @@ + + + + Specifies a set of object collections ("views") to be displayed (used) at + a specific location in the system. + This is generic type that can be used to specify collections to display + in role catalog, menu, dashboard and so on. + + + 3.6 + + + + + + + Reference to the colleciot, its visibility, etc. + + + 3.6 + + + + + + + + + + + Specifies how a specific collection ("view") is to be displayed (used) at + a specific location in the system. + This is generic type that can be used to specify collection to display + in role catalog, menu, dashboard and so on. + + + 3.6 + + + + + + + Reference to the built-in collection. + + + 3.6 + + + + + + + @@ -12449,7 +12577,8 @@ - TODO + Options for execution of Model operations. These options influence the way how the operations are executed. + The options are not mandatory. All options have reasonable default values. They may not be specified at all. @@ -12475,13 +12604,16 @@ Avoid encryption of any cleartext data on write. Applies only to the encrypted data formats (ProtectedString, ProtectedByteArray). + It is not recommended to use in production environment. This option is provided only for diagnostic + purposes to be used in development environments. - Option to reconcile user while executing changes. + Option to reconcile focus and all projections while executing changes. + (implies reconcileFocus) @@ -12557,7 +12689,8 @@ - Business context that describes this request. + Options that control selective execution of model logic. + Use with extreme care. Some combinations may be dangerous. @@ -12621,6 +12754,7 @@ + diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelExecuteOptions.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelExecuteOptions.java index ffdf9a4f7cc..0937b9bb88b 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelExecuteOptions.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelExecuteOptions.java @@ -25,8 +25,10 @@ import java.util.List; /** + * Options for execution of Model operations. These options influence the way how the operations are executed. + * The options are not mandatory. All options have reasonable default values. They may not be specified at all. + * * @author semancik - * */ public class ModelExecuteOptions extends AbstractOptions implements Serializable, Cloneable { @@ -46,6 +48,8 @@ public class ModelExecuteOptions extends AbstractOptions implements Serializable /** * Encrypt any cleartext data on write, decrypt any encrypted data on read. Applies only to the encrypted * data formats (ProtectedString, ProtectedByteArray). + * It is not recommended to use in production environment. This option is provided only for diagnostic + * purposes to be used in development environments. */ private Boolean noCrypt; @@ -88,6 +92,9 @@ public class ModelExecuteOptions extends AbstractOptions implements Serializable */ private Boolean overwrite; + /** + * Option to simulate import operation. E.g. search filters will be resolved. + */ private Boolean isImport; /** @@ -106,8 +113,15 @@ public class ModelExecuteOptions extends AbstractOptions implements Serializable */ private Boolean preAuthorized; + /** + * Business context that describes this request. + */ private OperationBusinessContextType requestBusinessContext; + /** + * Options that control selective execution of model logic. + * Use with extreme care. Some combinations may be dangerous. + */ private PartialProcessingOptionsType partialProcessing; public Boolean getForce() { diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/expr/MidpointFunctions.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/expr/MidpointFunctions.java index 42f349d0fc2..2358638b0aa 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/expr/MidpointFunctions.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/expr/MidpointFunctions.java @@ -28,6 +28,7 @@ import com.evolveum.midpoint.schema.ResultHandler; import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.security.api.MidPointPrincipal; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; @@ -1037,4 +1038,15 @@ List getMembers(String orgOid) List getMembersAsReferences(String orgOid) throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ObjectNotFoundException; + + /** + * Default function used to compute projection lifecycle. It is provided here so it can be explicitly + * invoked from a custom expression and then the result can be changed for special cases. + */ + String computeProjectionLifecycle(F focus, ShadowType shadow, ResourceType resource); + + /** + * Returns principal representing the user whose identity is used to execute the expression. + */ + MidPointPrincipal getPrincipal() throws SecurityViolationException; } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/Expression.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/Expression.java index ba53afc6d66..572d775aec5 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/Expression.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/Expression.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Evolveum + * Copyright (c) 2010-2017 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,22 +32,27 @@ import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismValue; import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.schema.internals.InternalsConfig; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.ObjectResolver; +import com.evolveum.midpoint.security.api.SecurityEnforcer; import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.exception.SystemException; +import com.evolveum.midpoint.util.exception.TunnelException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionVariableDefinitionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import com.evolveum.prism.xml.ns._public.types_3.RawType; /** @@ -56,15 +61,16 @@ */ public class Expression { - ExpressionType expressionType; - D outputDefinition; - PrismContext prismContext; - ObjectResolver objectResolver; - List> evaluators = new ArrayList>(1); + final private ExpressionType expressionType; + final private D outputDefinition; + final private PrismContext prismContext; + final private ObjectResolver objectResolver; + final private SecurityEnforcer securityEnforcer; + private List> evaluators = new ArrayList>(1); private static final Trace LOGGER = TraceManager.getTrace(Expression.class); - public Expression(ExpressionType expressionType, D outputDefinition, ObjectResolver objectResolver, PrismContext prismContext) { + public Expression(ExpressionType expressionType, D outputDefinition, ObjectResolver objectResolver, SecurityEnforcer securityEnforcer, PrismContext prismContext) { //Validate.notNull(outputDefinition, "null outputDefinition"); Validate.notNull(objectResolver, "null objectResolver"); Validate.notNull(prismContext, "null prismContext"); @@ -72,6 +78,7 @@ public Expression(ExpressionType expressionType, D outputDefinition, ObjectResol this.outputDefinition = outputDefinition; this.objectResolver = objectResolver; this.prismContext = prismContext; + this.securityEnforcer = securityEnforcer; } public void parse(ExpressionFactory factory, String contextDescription, Task task, OperationResult result) @@ -128,43 +135,92 @@ public PrismValueDeltaSetTriple evaluate(ExpressionEvaluationContext context) ExpressionEvaluationContext processedParameters = context.shallowClone(); processedParameters.setVariables(processedVariables); + PrismValueDeltaSetTriple outputTriple; - for (ExpressionEvaluator evaluator: evaluators) { - PrismValueDeltaSetTriple outputTriple = (PrismValueDeltaSetTriple) evaluator.evaluate(processedParameters); - if (outputTriple != null) { - boolean allowEmptyRealValues = false; - if (expressionType != null) { - allowEmptyRealValues = BooleanUtils.isTrue(expressionType.isAllowEmptyValues()); - } - outputTriple.removeEmptyValues(allowEmptyRealValues); - if (InternalsConfig.consistencyChecks) { + ObjectReferenceType runAsRef = null; + if (expressionType != null) { + runAsRef = expressionType.getRunAsRef(); + } + + if (runAsRef == null) { + + outputTriple = evaluateExpressionEvaluators(context, processedParameters); + + } else { + + UserType userType = objectResolver.resolve(runAsRef, UserType.class, null, + "runAs in "+context.getContextDescription(), context.getTask(), context.getResult()); + + LOGGER.trace("Running {} as {} ({})", context.getContextDescription(), userType, runAsRef); + + try { + + outputTriple = securityEnforcer.runAs(() -> { try { - outputTriple.checkConsistence(); - } catch (IllegalStateException e) { - throw new IllegalStateException(e.getMessage() + "; in expression " + this +", evaluator " + evaluator, e); + return evaluateExpressionEvaluators(context, processedParameters); + } catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException e) { + throw new TunnelException(e); } + }, userType.asPrismObject()); + + } catch (TunnelException te) { + Throwable e = te.getCause(); + if (e instanceof RuntimeException) { + throw (RuntimeException)e; + } + if (e instanceof Error) { + throw (Error)e; + } + if (e instanceof SchemaException) { + throw (SchemaException)e; } - traceSuccess(context, processedVariables, outputTriple); - return outputTriple; + if (e instanceof ExpressionEvaluationException) { + throw (ExpressionEvaluationException)e; + } + if (e instanceof ObjectNotFoundException) { + throw (ObjectNotFoundException)e; + } + throw te; } + } - traceSuccess(context, processedVariables, null); - return null; - } catch (SchemaException ex) { - traceFailure(context, processedVariables, ex); - throw ex; - } catch (ExpressionEvaluationException ex) { - traceFailure(context, processedVariables, ex); - throw ex; - } catch (ObjectNotFoundException ex) { - traceFailure(context, processedVariables, ex); - throw ex; - } catch (RuntimeException ex) { - traceFailure(context, processedVariables, ex); - throw ex; + + traceSuccess(context, processedVariables, outputTriple); + return outputTriple; + + } catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException | RuntimeException | Error e) { + traceFailure(context, processedVariables, e); + throw e; } } + + private PrismValueDeltaSetTriple evaluateExpressionEvaluators(ExpressionEvaluationContext context, ExpressionEvaluationContext processedParameters) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException { + + for (ExpressionEvaluator evaluator: evaluators) { + PrismValueDeltaSetTriple outputTriple = (PrismValueDeltaSetTriple) evaluator.evaluate(processedParameters); + if (outputTriple != null) { + boolean allowEmptyRealValues = false; + if (expressionType != null) { + allowEmptyRealValues = BooleanUtils.isTrue(expressionType.isAllowEmptyValues()); + } + outputTriple.removeEmptyValues(allowEmptyRealValues); + if (InternalsConfig.consistencyChecks) { + try { + outputTriple.checkConsistence(); + } catch (IllegalStateException e) { + throw new IllegalStateException(e.getMessage() + "; in expression " + this +", evaluator " + evaluator, e); + } + } + return outputTriple; + } + } + + return null; + + } + private void traceSuccess(ExpressionEvaluationContext context, ExpressionVariables processedVariables, PrismValueDeltaSetTriple outputTriple) { if (!isTrace()) { return; @@ -182,7 +238,7 @@ private void traceSuccess(ExpressionEvaluationContext context, ExpressionVariabl trace(sb.toString()); } - private void traceFailure(ExpressionEvaluationContext context, ExpressionVariables processedVariables, Exception e) { + private void traceFailure(ExpressionEvaluationContext context, ExpressionVariables processedVariables, Throwable e) { LOGGER.error("Error evaluating expression in {}: {}", new Object[]{context.getContextDescription(), e.getMessage(), e}); if (!isTrace()) { return; @@ -239,14 +295,19 @@ private void appendTraceFooter(StringBuilder sb) { private ExpressionVariables processInnerVariables(ExpressionVariables variables, String contextDescription, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException { - if (expressionType == null || expressionType.getVariable() == null || expressionType.getVariable().isEmpty()) { + if (expressionType == null) { // shortcut return variables; } ExpressionVariables newVariables = new ExpressionVariables(); + + // We need to add actor variable before we switch user identity (runAs) + ExpressionUtil.addActorVariable(newVariables, securityEnforcer); + for(Entry entry: variables.entrySet()) { newVariables.addVariableDefinition(entry.getKey(), entry.getValue()); } + for (ExpressionVariableDefinitionType variableDefType: expressionType.getVariable()) { QName varName = variableDefType.getName(); if (varName == null) { @@ -277,6 +338,7 @@ private ExpressionVariables processInnerVariables(ExpressionVariables variables, throw new SchemaException("No value for variable "+varName+" in "+contextDescription); } } + return newVariables; } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionFactory.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionFactory.java index a21add9ab34..9180afb9617 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionFactory.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Evolveum + * Copyright (c) 2010-2017 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import com.evolveum.midpoint.prism.PrismValue; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.ObjectResolver; +import com.evolveum.midpoint.security.api.SecurityEnforcer; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; @@ -40,13 +41,15 @@ public class ExpressionFactory { private Map evaluatorFactoriesMap = new HashMap(); private ExpressionEvaluatorFactory defaultEvaluatorFactory; private Map> cache = new HashMap>(); - private PrismContext prismContext; - private ObjectResolver objectResolver; + final private PrismContext prismContext; + final private ObjectResolver objectResolver; + final private SecurityEnforcer securityEnforcer; - public ExpressionFactory(ObjectResolver objectResolver, PrismContext prismContext) { + public ExpressionFactory(ObjectResolver objectResolver, SecurityEnforcer securityEnforcer, PrismContext prismContext) { super(); this.objectResolver = objectResolver; this.prismContext = prismContext; + this.securityEnforcer = securityEnforcer; } public PrismContext getPrismContext() { @@ -74,7 +77,7 @@ public Expression makeExpre private Expression createExpression(ExpressionType expressionType, D outputDefinition, String shortDesc, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException { - Expression expression = new Expression(expressionType, outputDefinition, objectResolver, prismContext); + Expression expression = new Expression(expressionType, outputDefinition, objectResolver, securityEnforcer, prismContext); expression.parse(this, shortDesc, task, result); return expression; } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java index 71891184f9c..2e7e55b744c 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java @@ -901,9 +901,10 @@ public static void addActorVariable(ExpressionVariables scriptVariables, try { if (securityEnforcer != null) { if (!securityEnforcer.isAuthenticated()) { - // Nothing to set up. This is most likely evaluation of role + // This is most likely evaluation of role // condition before // the authentication is complete. + scriptVariables.addVariableDefinition(ExpressionConstants.VAR_ACTOR, null); return; } MidPointPrincipal principal = securityEnforcer.getPrincipal(); diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/AbstractValueTransformationExpressionEvaluator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/AbstractValueTransformationExpressionEvaluator.java index 7b5ef546980..eb5c722542b 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/AbstractValueTransformationExpressionEvaluator.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/AbstractValueTransformationExpressionEvaluator.java @@ -76,8 +76,6 @@ public PrismValueDeltaSetTriple evaluate(ExpressionEvaluationContext context) PrismValueDeltaSetTriple outputTriple; - ExpressionUtil.addActorVariable(context.getVariables(), securityEnforcer); - if (expressionEvaluatorType.getRelativityMode() == TransformExpressionRelativityModeType.ABSOLUTE) { outputTriple = evaluateAbsoluteExpression(context.getSources(), context.getVariables(), context, diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluator.java index 7281e0a6933..6c3c2ee47b5 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluator.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluator.java @@ -24,7 +24,7 @@ import com.evolveum.midpoint.model.common.expression.ExpressionUtil; import com.evolveum.midpoint.model.common.expression.ExpressionVariables; import com.evolveum.midpoint.model.common.expression.StringPolicyResolver; -import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; import com.evolveum.midpoint.prism.Item; import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.PrismContext; @@ -62,11 +62,11 @@ public class GenerateExpressionEvaluator List evaluate(ScriptExpressionEvaluatorType try { InternalMonitor.recordScriptExecution(); evalRawResult = compiledScript.eval(bindings); - } catch (ScriptException e) { - throw new ExpressionEvaluationException(e.getMessage() + " " + contextDescription, e); + } catch (Throwable e) { + throw new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, e); } if (outputDefinition == null) { @@ -172,8 +172,8 @@ public Object evaluateReportScript(String codeString, ExpressionVariables va try { InternalMonitor.recordScriptExecution(); evalRawResult = compiledScript.eval(bindings); - } catch (ScriptException e) { - throw new ExpressionEvaluationException(e.getMessage() + " " + contextDescription, e); + } catch (Throwable e) { + throw new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, e); } @@ -190,7 +190,7 @@ private CompiledScript createCompiledScript(String codeString, String contextDes InternalMonitor.recordScriptCompile(); compiledScript = ((Compilable)scriptEngine).compile(codeString); } catch (ScriptException e) { - throw new ExpressionEvaluationException(e.getMessage() + " " + contextDescription, e); + throw new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, e); } scriptCache.put(codeString, compiledScript); return compiledScript; @@ -201,7 +201,7 @@ private T convertScalarResult(Class expectedType, Object rawValue, String T convertedValue = ExpressionUtil.convertValue(expectedType, rawValue, protector, prismContext); return convertedValue; } catch (IllegalArgumentException e) { - throw new ExpressionEvaluationException(e.getMessage()+" in "+contextDescription, e); + throw new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, e); } } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/Mapping.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/Mapping.java index 10752463d88..07856e0d5d2 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/Mapping.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/Mapping.java @@ -348,8 +348,6 @@ public void evaluate(Task task, OperationResult parentResult) throws ExpressionE OperationResult result = parentResult.createMinorSubresult(Mapping.class.getName()+".evaluate"); - ExpressionUtil.addActorVariable(variables, securityEnforcer); - traceEvaluationStart(); try { diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyGenerator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyProcessor.java similarity index 58% rename from model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyGenerator.java rename to model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyProcessor.java index d304845c365..0d3cd469453 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyGenerator.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyProcessor.java @@ -28,45 +28,54 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.Set; import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.Validate; import org.apache.commons.lang.text.StrBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import com.evolveum.midpoint.model.common.expression.Expression; import com.evolveum.midpoint.model.common.expression.ExpressionFactory; import com.evolveum.midpoint.model.common.expression.ExpressionUtil; import com.evolveum.midpoint.model.common.expression.ExpressionVariables; import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismPropertyDefinition; import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.prism.xml.XsdTypeMapper; import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CharacterClassType; import com.evolveum.midpoint.xml.ns._public.common.common_3.CheckExpressionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.LimitationsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordLifeTimeType; import com.evolveum.midpoint.xml.ns._public.common.common_3.StringLimitType; import com.evolveum.midpoint.xml.ns._public.common.common_3.StringPolicyType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType; @Component -public class ValuePolicyGenerator { +public class ValuePolicyProcessor { - private static final String OP_GENERATE = ValuePolicyGenerator.class.getName() + ".generate"; - private static final transient Trace LOGGER = TraceManager.getTrace(ValuePolicyGenerator.class); + private static final String OP_GENERATE = ValuePolicyProcessor.class.getName() + ".generate"; + private static final transient Trace LOGGER = TraceManager.getTrace(ValuePolicyProcessor.class); private static final Random RAND = new Random(System.currentTimeMillis()); + private static final String DOT_CLASS = ValuePolicyProcessor.class.getName() + "."; + private static final String OPERATION_STRING_POLICY_VALIDATION = DOT_CLASS + "stringPolicyValidation"; private static final int DEFAULT_MAX_ATTEMPTS = 10; @Autowired @@ -121,6 +130,292 @@ public String generate(StringPolicyType policy, int defa } + public boolean validateValue(String newValue, ValuePolicyType pp, + PrismObject object, String shortDesc, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { + return validateValue(newValue, pp, object, new StringBuilder(), shortDesc, task, parentResult); + } + + public boolean validateValue(String newValue, ValuePolicyType pp, + PrismObject object, StringBuilder message, String shortDesc, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { + + Validate.notNull(pp, "Password policy must not be null."); + + OperationResult result = parentResult.createSubresult(OPERATION_STRING_POLICY_VALIDATION); + result.addParam("policyName", pp.getName()); + normalize(pp); + + if (newValue == null && + (pp.getMinOccurs() == null || XsdTypeMapper.multiplicityToInteger(pp.getMinOccurs()) == 0)) { + // No password is allowed + result.recordSuccess(); + return true; + } + + if (newValue == null) { + newValue = ""; + } + + LimitationsType lims = pp.getStringPolicy().getLimitations(); + + testMinimalLength(newValue, lims, result, message); + testMaximalLength(newValue, lims, result, message); + + testMinimalUniqueCharacters(newValue, lims, result, message); + + if (lims.getLimit() == null || lims.getLimit().isEmpty()) { + if (message.toString() == null || message.toString().isEmpty()) { + result.computeStatus(); + } else { + result.computeStatus(message.toString()); + + } + + return result.isAcceptable(); + } + + // check limitation + HashSet validChars = null; + HashSet allValidChars = new HashSet<>(); + List passwd = StringPolicyUtils.stringTokenizer(newValue); + for (StringLimitType stringLimitationType : lims.getLimit()) { + OperationResult limitResult = new OperationResult( + "Tested limitation: " + stringLimitationType.getDescription()); + + validChars = getValidCharacters(stringLimitationType.getCharacterClass(), pp); + int count = countValidCharacters(validChars, passwd); + allValidChars.addAll(validChars); + testMinimalOccurence(stringLimitationType, count, limitResult, message); + testMaximalOccurence(stringLimitationType, count, limitResult, message); + testMustBeFirst(stringLimitationType, count, limitResult, message, newValue, validChars); + + limitResult.computeStatus(); + result.addSubresult(limitResult); + } + testInvalidCharacters(passwd, allValidChars, result, message); + + testCheckExpression(newValue, lims, object, shortDesc, task, result, message); + + if (message.toString() == null || message.toString().isEmpty()) { + result.computeStatus(); + } else { + result.computeStatus(message.toString()); + + } + + return result.isAcceptable(); + } + + /** + * add defined default values + */ + private void normalize(ValuePolicyType pp) { + if (null == pp) { + throw new IllegalArgumentException("Password policy cannot be null"); + } + + if (null == pp.getStringPolicy()) { + StringPolicyType sp = new StringPolicyType(); + pp.setStringPolicy(StringPolicyUtils.normalize(sp)); + } else { + pp.setStringPolicy(StringPolicyUtils.normalize(pp.getStringPolicy())); + } + + if (null == pp.getLifetime()) { + PasswordLifeTimeType lt = new PasswordLifeTimeType(); + lt.setExpiration(-1); + lt.setWarnBeforeExpiration(0); + lt.setLockAfterExpiration(0); + lt.setMinPasswordAge(0); + lt.setPasswordHistoryLength(0); + } + return; + } + + private void testMustBeFirst(StringLimitType stringLimitationType, int count, + OperationResult limitResult, StringBuilder message, String password, Set validChars) { + // test if first character is valid + if (stringLimitationType.isMustBeFirst() == null) { + stringLimitationType.setMustBeFirst(false); + } + // we check mustBeFirst only for non-empty passwords + if (StringUtils.isNotEmpty(password) && stringLimitationType.isMustBeFirst() + && !validChars.contains(password.substring(0, 1))) { + String msg = "First character is not from allowed set. Allowed set: " + validChars.toString(); + limitResult.addSubresult( + new OperationResult("Check valid first char", OperationResultStatus.FATAL_ERROR, msg)); + message.append(msg); + message.append("\n"); + } + // else { + // limitResult.addSubresult(new OperationResult("Check valid first char + // in password OK.", + // OperationResultStatus.SUCCESS, "PASSED")); + // } + + } + + private void testMaximalOccurence(StringLimitType stringLimitationType, int count, + OperationResult limitResult, StringBuilder message) { + // Test maximal occurrence + if (stringLimitationType.getMaxOccurs() != null) { + + if (stringLimitationType.getMaxOccurs() < count) { + String msg = "Required maximal occurrence (" + stringLimitationType.getMaxOccurs() + + ") of characters (" + stringLimitationType.getDescription() + + ") in password was exceeded (occurrence of characters in password " + count + ")."; + limitResult.addSubresult(new OperationResult("Check maximal occurrence of characters", + OperationResultStatus.FATAL_ERROR, msg)); + message.append(msg); + message.append("\n"); + } + // else { + // limitResult.addSubresult(new OperationResult( + // "Check maximal occurrence of characters in password OK.", + // OperationResultStatus.SUCCESS, + // "PASSED")); + // } + } + + } + + private void testMinimalOccurence(StringLimitType stringLimitation, int count, + OperationResult result, StringBuilder message) { + // Test minimal occurrence + if (stringLimitation.getMinOccurs() == null) { + stringLimitation.setMinOccurs(0); + } + if (stringLimitation.getMinOccurs() > count) { + String msg = "Required minimal occurrence (" + stringLimitation.getMinOccurs() + + ") of characters (" + stringLimitation.getDescription() + + ") in password is not met (occurrence of characters in password " + count + ")."; + result.addSubresult(new OperationResult("Check minimal occurrence of characters", + OperationResultStatus.FATAL_ERROR, msg)); + message.append(msg); + message.append("\n"); + } + } + + private int countValidCharacters(Set validChars, List password) { + int count = 0; + for (String s : password) { + if (validChars.contains(s)) { + count++; + } + } + return count; + } + + private HashSet getValidCharacters(CharacterClassType characterClassType, + ValuePolicyType passwordPolicy) { + if (null != characterClassType.getValue()) { + return new HashSet(StringPolicyUtils.stringTokenizer(characterClassType.getValue())); + } else { + return new HashSet(StringPolicyUtils.stringTokenizer(StringPolicyUtils + .collectCharacterClass(passwordPolicy.getStringPolicy().getCharacterClass(), + characterClassType.getRef()))); + } + } + + private void testMinimalUniqueCharacters(String password, LimitationsType limitations, + OperationResult result, StringBuilder message) { + // Test uniqueness criteria + HashSet tmp = new HashSet(StringPolicyUtils.stringTokenizer(password)); + if (limitations.getMinUniqueChars() != null) { + if (limitations.getMinUniqueChars() > tmp.size()) { + String msg = "Required minimal count of unique characters (" + limitations.getMinUniqueChars() + + ") in password are not met (unique characters in password " + tmp.size() + ")"; + result.addSubresult(new OperationResult("Check minimal count of unique chars", + OperationResultStatus.FATAL_ERROR, msg)); + message.append(msg); + message.append("\n"); + } + + } + } + + private void testMinimalLength(String password, LimitationsType limitations, + OperationResult result, StringBuilder message) { + // Test minimal length + if (limitations.getMinLength() == null) { + limitations.setMinLength(0); + } + if (limitations.getMinLength() > password.length()) { + String msg = "Required minimal size (" + limitations.getMinLength() + + ") of password is not met (password length: " + password.length() + ")"; + result.addSubresult(new OperationResult("Check global minimal length", + OperationResultStatus.FATAL_ERROR, msg)); + message.append(msg); + message.append("\n"); + } + } + + private void testMaximalLength(String password, LimitationsType limitations, + OperationResult result, StringBuilder message) { + // Test maximal length + if (limitations.getMaxLength() != null) { + if (limitations.getMaxLength() < password.length()) { + String msg = "Required maximal size (" + limitations.getMaxLength() + + ") of password was exceeded (password length: " + password.length() + ")."; + result.addSubresult(new OperationResult("Check global maximal length", + OperationResultStatus.FATAL_ERROR, msg)); + message.append(msg); + message.append("\n"); + } + } + } + + private void testInvalidCharacters(List password, HashSet validChars, + OperationResult result, StringBuilder message) { + + // Check if there is no invalid character + StringBuilder invalidCharacters = new StringBuilder(); + for (String s : password) { + if (!validChars.contains(s)) { + // memorize all invalid characters + invalidCharacters.append(s); + } + } + if (invalidCharacters.length() > 0) { + String msg = "Characters [ " + invalidCharacters + " ] are not allowed in password"; + result.addSubresult(new OperationResult("Check if password does not contain invalid characters", + OperationResultStatus.FATAL_ERROR, msg)); + message.append(msg); + message.append("\n"); + } + // else { + // ret.addSubresult(new OperationResult("Check if password does not + // contain invalid characters OK.", + // OperationResultStatus.SUCCESS, "PASSED")); + // } + + } + + private void testCheckExpression(String newPassword, LimitationsType lims, PrismObject object, + String shortDesc, Task task, OperationResult result, StringBuilder message) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { + + List checkExpressions = lims.getCheckExpression(); + if (checkExpressions.isEmpty()) { + return; + } + for (CheckExpressionType checkExpression: checkExpressions) { + ExpressionType expressionType = checkExpression.getExpression(); + if (expressionType == null) { + return; + } + if (!checkExpression(newPassword, expressionType, object, shortDesc, task, result)) { + String msg = checkExpression.getFailureMessage(); + if (msg == null) { + msg = "Check expression failed"; + } + result.addSubresult(new OperationResult("Check expression", + OperationResultStatus.FATAL_ERROR, msg)); + message.append(msg); + message.append("\n"); + } + } + + } + private String generateAttempt(StringPolicyType policy, int defaultLength, boolean generateMinimalSize, OperationResult result) { diff --git a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/ExpressionTestUtil.java b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/ExpressionTestUtil.java index 532d3faa553..1091e1d3e71 100644 --- a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/ExpressionTestUtil.java +++ b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/ExpressionTestUtil.java @@ -29,7 +29,7 @@ import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionFactory; import com.evolveum.midpoint.model.common.expression.script.jsr223.Jsr223ScriptEvaluator; import com.evolveum.midpoint.model.common.expression.script.xpath.XPathScriptEvaluator; -import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.crypto.ProtectorImpl; import com.evolveum.midpoint.schema.util.ObjectResolver; @@ -53,7 +53,7 @@ public static ProtectorImpl createInitializedProtector(PrismContext prismContext public static ExpressionFactory createInitializedExpressionFactory(ObjectResolver resolver, ProtectorImpl protector, PrismContext prismContext, SecurityEnforcer securityEnforcer) { - ExpressionFactory expressionFactory = new ExpressionFactory(resolver, prismContext); + ExpressionFactory expressionFactory = new ExpressionFactory(resolver, securityEnforcer, prismContext); // asIs AsIsExpressionEvaluatorFactory asIsFactory = new AsIsExpressionEvaluatorFactory(prismContext, protector); @@ -69,7 +69,7 @@ public static ExpressionFactory createInitializedExpressionFactory(ObjectResolve expressionFactory.addEvaluatorFactory(pathFactory); // generate - ValuePolicyGenerator valuePolicyGenerator = new ValuePolicyGenerator(); + ValuePolicyProcessor valuePolicyGenerator = new ValuePolicyProcessor(); valuePolicyGenerator.setExpressionFactory(expressionFactory); GenerateExpressionEvaluatorFactory generateFactory = new GenerateExpressionEvaluatorFactory(protector, resolver, valuePolicyGenerator, prismContext); expressionFactory.addEvaluatorFactory(generateFactory); diff --git a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/mapping/TestMappingStatic.java b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/mapping/TestMappingStatic.java index f20ebddb9a5..6b6b6011724 100644 --- a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/mapping/TestMappingStatic.java +++ b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/mapping/TestMappingStatic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2013 Evolveum + * Copyright (c) 2010-2017 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -94,33 +94,6 @@ public void testValueMultiShallow() throws Exception { PrismAsserts.assertTripleNoPlus(outputTriple); PrismAsserts.assertTripleNoMinus(outputTriple); } - - // We do not have boolean property in the use any more ... but this is covered quite well in dynamic tests -// @Test -// public void testValueBooleanTrue() throws Exception { -// // WHEN -// PrismValueDeltaSetTriple> outputTriple = evaluator.evaluateMapping( -// "mapping-value-boolean-true.xml", -// "testValue", -// new ItemPath(UserType.F_CREDENTIALS, CredentialsType.F_ALLOWED_IDM_ADMIN_GUI_ACCESS)); // target -// // THEN -// PrismAsserts.assertTripleZero(outputTriple, Boolean.TRUE); -// PrismAsserts.assertTripleNoPlus(outputTriple); -// PrismAsserts.assertTripleNoMinus(outputTriple); -// } -// -// @Test -// public void testValueBooleanFalse() throws Exception { -// // WHEN -// PrismValueDeltaSetTriple> outputTriple = evaluator.evaluateMapping( -// "mapping-value-boolean-false.xml", -// "testValue", -// new ItemPath(UserType.F_CREDENTIALS, CredentialsType.F_ALLOWED_IDM_ADMIN_GUI_ACCESS)); // target -// // THEN -// PrismAsserts.assertTripleZero(outputTriple, Boolean.FALSE); -// PrismAsserts.assertTripleNoPlus(outputTriple); -// PrismAsserts.assertTripleNoMinus(outputTriple); -// } @Test public void testPathNoSource() throws Exception { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelRestService.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelRestService.java index 7796c8ab62d..d0ca3ccccd8 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelRestService.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelRestService.java @@ -16,6 +16,7 @@ package com.evolveum.midpoint.model.impl; import java.net.URI; +import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -38,6 +39,8 @@ import javax.xml.bind.JAXBException; import javax.xml.namespace.QName; +import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.apache.cxf.jaxrs.ext.MessageContext; import org.springframework.beans.factory.annotation.Autowired; @@ -53,14 +56,17 @@ import com.evolveum.midpoint.model.api.validator.ResourceValidator; import com.evolveum.midpoint.model.api.validator.Scope; import com.evolveum.midpoint.model.api.validator.ValidationResult; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; import com.evolveum.midpoint.model.impl.rest.PATCH; import com.evolveum.midpoint.model.impl.security.SecurityHelper; import com.evolveum.midpoint.model.impl.util.RestServiceUtil; import com.evolveum.midpoint.prism.Item; +import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismValue; import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.delta.PropertyDelta; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.QueryJaxbConvertor; @@ -75,16 +81,21 @@ import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.task.api.TaskManager; import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; -import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.PolicyViolationException; import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.CompareResultType; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ObjectListType; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ObjectModificationType; +import com.evolveum.midpoint.xml.ns._public.common.api_types_3.PolicyItemDefinitionType; +import com.evolveum.midpoint.xml.ns._public.common.api_types_3.PolicyItemsDefinitionType; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ScriptOutputsType; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.SingleScriptOutputType; import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsPolicyType; @@ -94,8 +105,10 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectShadowChangeDescriptionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.StringPolicyType; import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType; import com.evolveum.midpoint.xml.ns._public.model.model_3.ExecuteScriptsResponseType; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ItemListType; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ScriptingExpressionType; @@ -153,6 +166,9 @@ public class ModelRestService { @Autowired private SecurityHelper securityHelper; + @Autowired + ValuePolicyProcessor policyProcessor; + @Autowired private TaskManager taskManager; @@ -166,6 +182,165 @@ public class ModelRestService { public ModelRestService() { // nothing to do } + + @POST + @Path("/{type}/{oid}/generate") + @Consumes({"application/xml", "application/json", "application/yaml"}) + @Produces({"application/xml", "application/json", "application/yaml"}) + public Response generateValue(@PathParam("type") String type, + @PathParam("oid") String oid, PolicyItemsDefinitionType policyItemsDefinition, + @Context MessageContext mc) { + + Task task = RestServiceUtil.initRequest(mc); + OperationResult parentResult = task.getResult().createSubresult(OPERATION_GET); + + Class clazz = ObjectTypes.getClassFromRestType(type); + Response response = null; + if (policyItemsDefinition == null) { + LOGGER.error("Policy items definition must not be null"); + parentResult.recordFatalError("Policy items definition must not be null"); + ResponseBuilder builder = Response.status(Status.BAD_REQUEST).entity(parentResult); + return builder.build(); + } + try { + PrismObject object = model.getObject(clazz, oid, null, task, parentResult); + + PrismObject valuePolicy = resolveUserPolicy((PrismObject) object, task, + parentResult); + + boolean executeImmediatelly = false; + Collection propertyDeltas = new ArrayList<>(); + for (PolicyItemDefinitionType policyItemDefinition : policyItemsDefinition + .getPolicyItemDefinition()) { + + generateValue(object, valuePolicy, policyItemDefinition, task, parentResult); + + if (BooleanUtils.isTrue(policyItemDefinition.isExecute())) { + executeImmediatelly = true; + PropertyDelta propertyDelta = PropertyDelta.createModificationReplaceProperty(policyItemDefinition.getTarget().getPath().getItemPath(), object.getDefinition(), policyItemDefinition.getValue()); + propertyDeltas.add(propertyDelta); + } + + } + + if (executeImmediatelly) { + model.modifyObject(clazz, oid, propertyDeltas, null, task, parentResult); + } + + ResponseBuilder responseBuilder = Response.ok(policyItemsDefinition); + response = responseBuilder.build(); + } catch (Exception ex) { + response = RestServiceUtil.handleException(parentResult, ex); + } + + parentResult.computeStatus(); + finishRequest(task); + return response; + + } + + private PrismObject resolveUserPolicy(PrismObject user, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + CredentialsPolicyType policy = modelInteraction.getCredentialsPolicy(user, task, parentResult); + + + PrismObject valuePolicy = null; + if (policy.getPassword().getPasswordPolicyRef() != null) { + valuePolicy = model.getObject(ValuePolicyType.class, policy.getPassword().getPasswordPolicyRef().getOid(), null, task, parentResult); + } + + return valuePolicy; + } + + private void generateValue(PrismObject object, PrismObject policy, PolicyItemDefinitionType policyItemDefinition, Task task, OperationResult result) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + + StringPolicyType stringPolicy = null; + if (policyItemDefinition.getValuePolicyRef() != null) { + PrismObject valuePolicy = model.getObject(ValuePolicyType.class, policyItemDefinition.getValuePolicyRef().getOid(), null, task, result); + PrismObject policyOverride = valuePolicy.clone(); + stringPolicy = policyOverride != null ? policyOverride.asObjectable().getStringPolicy() : null; + } else { + + stringPolicy = policy != null ? policy.asObjectable().getStringPolicy() : null; + } + String newValue = policyProcessor.generate(stringPolicy, 10, object, "generating value for" + policyItemDefinition.getTarget().getPath(), task, result); + policyItemDefinition.setValue(newValue); + } + + @POST + @Path("/{type}/{oid}/validate") + public Response validateValue(@PathParam("type") String type, @PathParam("oid") String oid, PolicyItemsDefinitionType policyItemsDefinition, @Context MessageContext mc) { + + Task task = RestServiceUtil.initRequest(mc); + OperationResult parentResult = task.getResult().createSubresult(OPERATION_GET); + + Class clazz = ObjectTypes.getClassFromRestType(type); + Response response = null; + if (policyItemsDefinition == null) { + LOGGER.error("Policy items definition must not be null"); + parentResult.recordFatalError("Policy items definition must not be null"); + ResponseBuilder builder = Response.status(Status.BAD_REQUEST).entity(parentResult); + return builder.build(); + } + try { + PrismObject object = model.getObject(clazz, oid, null, task, parentResult); + + PrismObject valuePolicy = resolveUserPolicy((PrismObject) object, task, + parentResult); + + boolean executeImmediatelly = false; + Collection propertyDeltas = new ArrayList<>(); + for (PolicyItemDefinitionType policyItemDefinition : policyItemsDefinition + .getPolicyItemDefinition()) { + + validateValue(object, valuePolicy, policyItemDefinition, task, parentResult); + + } + + + + ResponseBuilder responseBuilder = Response.ok(policyItemsDefinition); + response = responseBuilder.build(); + } catch (Exception ex) { + response = RestServiceUtil.handleException(parentResult, ex); + } + + parentResult.computeStatus(); + finishRequest(task); + return response; + + + } + +private void validateValue(PrismObject object, PrismObject policy, PolicyItemDefinitionType policyItemDefinition, Task task, OperationResult result) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException { + + ValuePolicyType stringPolicy = null; + if (policyItemDefinition.getValuePolicyRef() != null) { + PrismObject valuePolicy = model.getObject(ValuePolicyType.class, policyItemDefinition.getValuePolicyRef().getOid(), null, task, result); + PrismObject policyOverride = valuePolicy.clone(); + stringPolicy = policyOverride != null ? policyOverride.asObjectable() : null; + } else { + + stringPolicy = policy != null ? policy.asObjectable() : null; + } + + ItemDefinition itemToValidateDefinition = object.findItem(policyItemDefinition.getTarget().getPath().getItemPath()).getDefinition(); + itemToValidateDefinition.getTypeName(); + + prismContext.getEntityResolver(); + + //TODO + String valueToValidate = (String) policyItemDefinition.getValue(); + if (StringUtils.isBlank(valueToValidate)) { + valueToValidate = object.findItem(policyItemDefinition.getTarget().getPath().getItemPath()).getRealValue(); + } + + if (!policyProcessor.validateValue(valueToValidate, stringPolicy, object, "validate value for " + object + " value " + valueToValidate, task, result)) { + result.computeStatus(); + throw new PolicyViolationException("Validation for value: " + valueToValidate + " failed." + result.getMessage()); + } + + } + @GET @Path("/users/{id}/policy") @@ -177,6 +352,7 @@ public Response getValuePolicyForUser(@PathParam("id") String oid, @Context Mess Response response; try { + Collection> options = SelectorOptions.createCollection(GetOperationOptions.createRaw()); PrismObject user = model.getObject(UserType.class, oid, options, task, parentResult); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java index e7614418cbb..a8865af0647 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java @@ -23,7 +23,7 @@ import com.evolveum.midpoint.model.api.*; import com.evolveum.midpoint.model.api.visualizer.Scene; import com.evolveum.midpoint.model.common.SystemObjectCache; -import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; import com.evolveum.midpoint.model.impl.visualizer.Visualizer; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; @@ -122,7 +122,7 @@ public class ModelInteractionServiceImpl implements ModelInteractionService { private SystemObjectCache systemObjectCache; @Autowired(required = true) - private ValuePolicyGenerator valuePolicyGenerator; + private ValuePolicyProcessor valuePolicyGenerator; @Autowired(required = true) private Protector protector; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java index 248ab2c38fa..bd47fc3c04e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java @@ -44,8 +44,11 @@ import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.ResultHandler; import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.*; +import com.evolveum.midpoint.security.api.MidPointPrincipal; +import com.evolveum.midpoint.security.api.SecurityEnforcer; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.Holder; import com.evolveum.midpoint.util.exception.CommunicationException; @@ -60,6 +63,9 @@ import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationCapabilityType; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CredentialsCapabilityType; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.PasswordCapabilityType; import org.apache.commons.lang.Validate; import org.jetbrains.annotations.NotNull; @@ -119,6 +125,9 @@ public class MidpointFunctionsImpl implements MidpointFunctions { @Autowired private ProvisioningService provisioningService; + + @Autowired + private SecurityEnforcer securityEnforcer; @Autowired(required = true) private transient Protector protector; @@ -1241,4 +1250,37 @@ public List getMembers(String orgOid) throws SchemaException, ObjectNo .build(); return searchObjects(UserType.class, query, null); } + + @Override + public String computeProjectionLifecycle(F focus, ShadowType shadow, ResourceType resource) { + if (focus == null || shadow == null) { + return null; + } + if (!(focus instanceof UserType)) { + return null; + } + if (shadow.getKind() != null && shadow.getKind() != ShadowKindType.ACCOUNT) { + return null; + } + ProtectedStringType passwordPs = FocusTypeUtil.getPasswordValue((UserType)focus); + if (passwordPs != null && passwordPs.canGetCleartext()) { + return null; + } + CredentialsCapabilityType credentialsCapabilityType = ResourceTypeUtil.getEffectiveCapability(resource, CredentialsCapabilityType.class); + if (credentialsCapabilityType == null) { + return null; + } + PasswordCapabilityType passwordCapabilityType = credentialsCapabilityType.getPassword(); + if (passwordCapabilityType == null) { + return null; + } + if (passwordCapabilityType.isEnabled() == Boolean.FALSE) { + return null; + } + return SchemaConstants.LIFECYCLE_PROPOSED; + } + + public MidPointPrincipal getPrincipal() throws SecurityViolationException { + return securityEnforcer.getPrincipal(); + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java index 6e50a3db262..59c5c2ab424 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java @@ -15,8 +15,10 @@ */ package com.evolveum.midpoint.model.impl.lens; +import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; @@ -60,21 +62,7 @@ import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractRoleType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentSelectorType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ConstructionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingsType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.PolicyConstraintsType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.PolicyRuleType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -636,7 +624,7 @@ private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, PlusMinusZ ctx.evalAssignment.addRole(evalAssignmentTarget, mode); if ((isNonNegative(mode)) && segment.isProcessMembership()) { - evaluateMembership(targetType, relation, ctx); + collectMembership(targetType, relation, ctx); } // We continue evaluation even if the relation is non-membership and non-delegation. @@ -655,10 +643,13 @@ private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, PlusMinusZ if (matchesOrder && targetType instanceof AbstractRoleType && isNonNegative(mode)) { for (AuthorizationType authorizationType: ((AbstractRoleType)targetType).getAuthorization()) { Authorization authorization = createAuthorization(authorizationType, targetType.toString()); - ctx.evalAssignment.addAuthorization(authorization); + if (!ctx.evalAssignment.getAuthorizations().contains(authorization)) { + ctx.evalAssignment.addAuthorization(authorization); + } } - if (((AbstractRoleType)targetType).getAdminGuiConfiguration() != null) { - ctx.evalAssignment.addAdminGuiConfiguration(((AbstractRoleType)targetType).getAdminGuiConfiguration()); + AdminGuiConfigurationType adminGuiConfiguration = ((AbstractRoleType) targetType).getAdminGuiConfiguration(); + if (adminGuiConfiguration != null && !ctx.evalAssignment.getAdminGuiConfigurations().contains(adminGuiConfiguration)) { + ctx.evalAssignment.addAdminGuiConfiguration(adminGuiConfiguration); } PolicyConstraintsType policyConstraints = ((AbstractRoleType)targetType).getPolicyConstraints(); if (policyConstraints != null) { @@ -792,7 +783,7 @@ private void evaluateInducement(AssignmentPathSegmentImpl segment, PlusMinusZero evaluateFromSegment(nextSegment, mode, ctx); } - private void evaluateMembership(FocusType targetType, QName relation, EvaluationContext ctx) { + private void collectMembership(FocusType targetType, QName relation, EvaluationContext ctx) { PrismReferenceValue refVal = new PrismReferenceValue(); refVal.setObject(targetType.asPrismObject()); refVal.setTargetType(ObjectTypes.getObjectType(targetType.getClass()).getTypeQName()); @@ -800,18 +791,27 @@ private void evaluateMembership(FocusType targetType, QName relation, Evaluation refVal.setTargetName(targetType.getName().toPolyString()); if (ctx.assignmentPath.getSegments().stream().anyMatch(aps -> DeputyUtils.isDelegationAssignment(aps.getAssignment()))) { - LOGGER.trace("Adding target {} to delegationRef", targetType); - ctx.evalAssignment.addDelegationRefVal(refVal); + addIfNotThere(ctx.evalAssignment.getDelegationRefVals(), ctx.evalAssignment::addDelegationRefVal, refVal, + "delegationRef", targetType); } else { if (targetType instanceof AbstractRoleType) { - LOGGER.trace("Adding target {} to membershipRef", targetType); - ctx.evalAssignment.addMembershipRefVal(refVal); + addIfNotThere(ctx.evalAssignment.getMembershipRefVals(), ctx.evalAssignment::addMembershipRefVal, refVal, + "membershipRef", targetType); } } - if (targetType instanceof OrgType) { - LOGGER.trace("Adding target {} to orgRef", targetType); - ctx.evalAssignment.addOrgRefVal(refVal); + addIfNotThere(ctx.evalAssignment.getOrgRefVals(), ctx.evalAssignment::addOrgRefVal, refVal, + "orgRef", targetType); + } + } + + private void addIfNotThere(Collection collection, Consumer setter, + PrismReferenceValue refVal, String collectionName, FocusType targetType) { + if (!collection.contains(refVal)) { + LOGGER.trace("Adding target {} to {}", targetType, collectionName); + setter.accept(refVal); + } else { + LOGGER.trace("Would add target {} to {}, but it's already there", targetType, collectionName); } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java index 4f09dc5f197..a14182a976a 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java @@ -246,7 +246,7 @@ public String getIntent() { return refinedObjectClassDefinition.getIntent(); } - public Object getDescription() { + public String getDescription() { return constructionType.getDescription(); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java index 0ee2517c333..4f0835edb45 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java @@ -198,9 +198,6 @@ public void addDelegationRefVal(PrismReferenceValue org) { delegationRefVals.add(org); } - /* (non-Javadoc) - * @see com.evolveum.midpoint.model.impl.lens.EvaluatedAssignment#getAuthorizations() - */ @NotNull @Override public Collection getAuthorizations() { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java index 4377d447aa2..6b9d596fe47 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java @@ -59,7 +59,7 @@ public class LensContext implements ModelContext { private String channel; private LensFocusContext focusContext; - private Collection projectionContexts = new ArrayList(); + private Collection projectionContexts = new ArrayList<>(); /** * EXPERIMENTAL. A trace of resource objects that once existed but were @@ -92,7 +92,7 @@ public class LensContext implements ModelContext { /* * Executed deltas from rotten contexts. */ - private List> rottenExecutedDeltas = new ArrayList>(); + private List> rottenExecutedDeltas = new ArrayList<>(); transient private ObjectTemplateType focusTemplate; transient private ProjectionPolicyType accountSynchronizationSettings; @@ -188,10 +188,6 @@ public ProvisioningService getProvisioningService() { return provisioningService; } - public void setTriggeredResource(String triggeredResourceOid) { - this.triggeredResourceOid = triggeredResourceOid; - } - public void setTriggeredResource(ResourceType triggeredResource) { if (triggeredResource != null) { this.triggeredResourceOid = triggeredResource.getOid(); @@ -228,7 +224,7 @@ public LensFocusContext createFocusContext(Class explicitFocusClass) { if (explicitFocusClass != null) { this.focusClass = explicitFocusClass; } - focusContext = new LensFocusContext(focusClass, this); + focusContext = new LensFocusContext<>(focusClass, this); return focusContext; } @@ -524,7 +520,7 @@ public OperationBusinessContextType getRequestBusinessContext() { * better to merge them. */ public Collection> getAllChanges() throws SchemaException { - Collection> allChanges = new ArrayList>(); + Collection> allChanges = new ArrayList<>(); if (focusContext != null) { addChangeIfNotNull(allChanges, focusContext.getPrimaryDelta()); addChangeIfNotNull(allChanges, focusContext.getSecondaryDelta()); @@ -551,7 +547,7 @@ public boolean hasAnyPrimaryChange() throws SchemaException { } public Collection> getPrimaryChanges() throws SchemaException { - Collection> allChanges = new ArrayList>(); + Collection> allChanges = new ArrayList<>(); if (focusContext != null) { addChangeIfNotNull(allChanges, focusContext.getPrimaryDelta()); } @@ -603,7 +599,7 @@ public Collection> getUnauditedExecut */ Collection> getExecutedDeltas(Boolean audited) throws SchemaException { - Collection> executedDeltas = new ArrayList>(); + Collection> executedDeltas = new ArrayList<>(); if (focusContext != null) { executedDeltas.addAll(focusContext.getExecutedDeltas(audited)); } @@ -611,7 +607,7 @@ Collection> getExecutedDeltas(Boolean executedDeltas.addAll(projCtx.getExecutedDeltas(audited)); } if (audited == null) { - executedDeltas.addAll(getRottenExecutedDeltas()); + executedDeltas.addAll((Collection>) getRottenExecutedDeltas()); } return executedDeltas; } @@ -696,7 +692,7 @@ public LensProjectionContext createProjectionContext(ResourceShadowDiscriminator private Map getResourceCache() { if (resourceCache == null) { - resourceCache = new HashMap(); + resourceCache = new HashMap<>(); } return resourceCache; } @@ -780,7 +776,7 @@ public void normalize() { } public LensContext clone() { - LensContext clone = new LensContext(focusClass, prismContext, provisioningService); + LensContext clone = new LensContext<>(focusClass, prismContext, provisioningService); copyValues(clone); return clone; } @@ -813,7 +809,7 @@ private Map cloneResourceCache() { if (resourceCache == null) { return null; } - Map clonedMap = new HashMap(); + Map clonedMap = new HashMap<>(); for (Entry entry : resourceCache.entrySet()) { clonedMap.put(entry.getKey(), entry.getValue()); } @@ -977,31 +973,40 @@ private void dumpPolicyRulesCollection(String label, int indent, StringBuilder s } } - private void dumpEvaluatedAssignments(StringBuilder sb, String label, - Collection> set, int indent) { + // of LensContext is of ObjectType; so we need to override it (but using another name to avoid IDE warnings) + private void dumpEvaluatedAssignments(StringBuilder sb, String label, + Collection> set, int indent) { sb.append("\n"); DebugUtil.debugDumpLabel(sb, label, indent); - for (EvaluatedAssignmentImpl assignment : set) { + for (EvaluatedAssignmentImpl assignment : set) { sb.append("\n"); DebugUtil.indentDebugDump(sb, indent + 1); - sb.append(" -> " + assignment.getTarget()); - dumpRules(sb, "focus rules", assignment.getFocusPolicyRules()); - dumpRules(sb, "this target rules", assignment.getThisTargetPolicyRules()); - dumpRules(sb, "other targets rules", assignment.getOtherTargetsPolicyRules()); + sb.append("-> ").append(assignment.getTarget()); + dumpRules(sb, "focus rules", indent + 3, assignment.getFocusPolicyRules()); + dumpRules(sb, "this target rules", indent + 3, assignment.getThisTargetPolicyRules()); + dumpRules(sb, "other targets rules", indent + 3, assignment.getOtherTargetsPolicyRules()); } } - private void dumpRules(StringBuilder sb, String label, Collection policyRules) { - sb.append(" [").append(label).append("(").append(policyRules.size()).append("): "); + private void dumpRules(StringBuilder sb, String label, int indent, Collection policyRules) { + if (policyRules.isEmpty()) { + return; + } + sb.append("\n"); + DebugUtil.debugDumpLabel(sb, "- " + label + " (" + policyRules.size() + ")", indent); boolean first = true; for (EvaluatedPolicyRule rule : policyRules) { if (first) { first = false; + sb.append(" "); } else { sb.append("; "); } if (rule.isGlobal()) { - sb.append("G"); + sb.append("G:"); + } + if (rule.getName() != null) { + sb.append(rule.getName()); } sb.append("(").append(PolicyRuleTypeUtil.toShortString(rule.getPolicyConstraints())).append(")"); sb.append("->"); @@ -1013,7 +1018,6 @@ private void dumpRules(StringBuilder sb, String label, Col sb.append(")"); } } - sb.append("]"); } public LensContextType toLensContextType() throws SchemaException { @@ -1059,6 +1063,7 @@ public PrismContainer toPrismContainer() throws SchemaException return lensContextTypeContainer; } + @SuppressWarnings({"unchecked", "raw"}) public static LensContext fromLensContextType(LensContextType lensContextType, PrismContext prismContext, ProvisioningService provisioningService, OperationResult parentResult) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java index 73496f6a50f..c9798441d4e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java @@ -1205,16 +1205,34 @@ public static void processRuleWithException(EvaluatedPolicyRule rule, EvaluatedP } + public static void partialExecute(String componentName, ProjectorComponentRunnable runnable, Supplier optionSupplier) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException { + partialExecute(componentName, runnable, optionSupplier, null); + } + + public static void partialExecute(String componentName, ProjectorComponentRunnable runnable, + Supplier optionSupplier, OperationResult result) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, + PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException { PartialProcessingTypeType option = optionSupplier.get(); if (option == PartialProcessingTypeType.SKIP) { LOGGER.debug("Skipping projector component {} because partial execution option is set to {}", componentName, option); } else { LOGGER.trace("Projector component started: {}", componentName); - runnable.run(); - LOGGER.trace("Projector component finished: {}", componentName); + try { + runnable.run(); + LOGGER.trace("Projector component finished: {}", componentName); + } catch (SchemaException | ObjectNotFoundException | CommunicationException | ConfigurationException | SecurityViolationException + | PolicyViolationException | ExpressionEvaluationException | ObjectAlreadyExistsException | RuntimeException | Error e) { + LOGGER.trace("Projector component error: {}: {}: {}", componentName, e.getClass().getSimpleName(), e.getMessage()); + if (result != null) { + result.recordFatalError(e); + } + throw e; + } + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java index 33d875c3d31..e52d59babaa 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Evolveum + * Copyright (c) 2010-2017 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,9 @@ */ package com.evolveum.midpoint.model.impl.lens.projector; +import com.evolveum.midpoint.common.refinery.CompositeRefinedObjectClassDefinition; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; +import com.evolveum.midpoint.model.api.expr.MidpointFunctions; import com.evolveum.midpoint.model.common.expression.ExpressionUtil; import com.evolveum.midpoint.model.common.expression.ItemDeltaItem; import com.evolveum.midpoint.model.common.expression.Source; @@ -50,6 +52,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceActivationDefinitionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceBidirectionalMappingType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectLifecycleDefinitionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectTypeDefinitionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; @@ -92,6 +95,9 @@ public class ActivationProcessor { @Autowired private MappingEvaluator mappingHelper; + + @Autowired + private MidpointFunctions midpointFunctions; public void processActivation(LensContext context, LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { @@ -117,12 +123,12 @@ private void processActivationFocal(LensContext context processActivationUserFuture(context, projectionContext, now, task, result); } - public void processActivationUserCurrent(LensContext context, LensProjectionContext accCtx, + public void processActivationUserCurrent(LensContext context, LensProjectionContext projCtx, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { - String accCtxDesc = accCtx.toHumanReadableString(); - SynchronizationPolicyDecision decision = accCtx.getSynchronizationPolicyDecision(); - SynchronizationIntent synchronizationIntent = accCtx.getSynchronizationIntent(); + String accCtxDesc = projCtx.toHumanReadableString(); + SynchronizationPolicyDecision decision = projCtx.getSynchronizationPolicyDecision(); + SynchronizationIntent synchronizationIntent = projCtx.getSynchronizationIntent(); if (decision == SynchronizationPolicyDecision.BROKEN) { LOGGER.trace("Broken projection {}, skipping further activation processing", accCtxDesc); @@ -132,31 +138,31 @@ public void processActivationUserCurrent(LensContext co throw new IllegalStateException("Decision "+decision+" already present for projection "+accCtxDesc); } - if (accCtx.isThombstone() || synchronizationIntent == SynchronizationIntent.UNLINK) { - accCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.UNLINK); + if (projCtx.isThombstone() || synchronizationIntent == SynchronizationIntent.UNLINK) { + projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.UNLINK); LOGGER.trace("Evaluated decision for {} to {}, skipping further activation processing", accCtxDesc, SynchronizationPolicyDecision.UNLINK); return; } - if (synchronizationIntent == SynchronizationIntent.DELETE || accCtx.isDelete()) { + if (synchronizationIntent == SynchronizationIntent.DELETE || projCtx.isDelete()) { // TODO: is this OK? - accCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.DELETE); + projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.DELETE); LOGGER.trace("Evaluated decision for {} to {}, skipping further activation processing", accCtxDesc, SynchronizationPolicyDecision.DELETE); return; } - boolean shadowShouldExist = evaluateExistenceMapping(context, accCtx, now, true, task, result); + boolean shadowShouldExist = evaluateExistenceMapping(context, projCtx, now, true, task, result); LOGGER.trace("Evaluated intended existence of projection {} to {}", accCtxDesc, shadowShouldExist); // Let's reconcile the existence intent (shadowShouldExist) and the synchronization intent in the context - LensProjectionContext lowerOrderContext = LensUtil.findLowerOrderContext(context, accCtx); + LensProjectionContext lowerOrderContext = LensUtil.findLowerOrderContext(context, projCtx); if (synchronizationIntent == null || synchronizationIntent == SynchronizationIntent.SYNCHRONIZE) { if (shadowShouldExist) { - accCtx.setActive(true); - if (accCtx.isExists()) { + projCtx.setActive(true); + if (projCtx.isExists()) { if (lowerOrderContext != null && lowerOrderContext.isDelete()) { // HACK HACK HACK decision = SynchronizationPolicyDecision.DELETE; @@ -180,21 +186,21 @@ public void processActivationUserCurrent(LensContext co } } else { // Delete - if (accCtx.isExists()) { + if (projCtx.isExists()) { decision = SynchronizationPolicyDecision.DELETE; } else { // we should delete the entire context, but then we will lost track of what // happened. So just ignore it. decision = SynchronizationPolicyDecision.IGNORE; // if there are any triggers then move them to focus. We may still need them. - LensUtil.moveTriggers(accCtx, context.getFocusContext()); + LensUtil.moveTriggers(projCtx, context.getFocusContext()); } } } else if (synchronizationIntent == SynchronizationIntent.ADD) { if (shadowShouldExist) { - accCtx.setActive(true); - if (accCtx.isExists()) { + projCtx.setActive(true); + if (projCtx.isExists()) { // Attempt to add something that is already there, but should be OK decision = SynchronizationPolicyDecision.KEEP; } else { @@ -206,8 +212,8 @@ public void processActivationUserCurrent(LensContext co } else if (synchronizationIntent == SynchronizationIntent.KEEP) { if (shadowShouldExist) { - accCtx.setActive(true); - if (accCtx.isExists()) { + projCtx.setActive(true); + if (projCtx.isExists()) { decision = SynchronizationPolicyDecision.KEEP; } else { decision = SynchronizationPolicyDecision.ADD; @@ -222,7 +228,7 @@ public void processActivationUserCurrent(LensContext co LOGGER.trace("Evaluated decision for projection {} to {}", accCtxDesc, decision); - accCtx.setSynchronizationPolicyDecision(decision); + projCtx.setSynchronizationPolicyDecision(decision); PrismObject focusNew = context.getFocusContext().getObjectNew(); if (focusNew == null) { @@ -236,7 +242,7 @@ public void processActivationUserCurrent(LensContext co return; } - ResourceObjectTypeDefinitionType resourceAccountDefType = accCtx.getResourceObjectTypeDefinitionType(); + ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); if (resourceAccountDefType == null) { LOGGER.trace("No refined object definition, therefore also no activation outbound definition, skipping activation processing for account " + accCtxDesc); return; @@ -247,9 +253,9 @@ public void processActivationUserCurrent(LensContext co return; } - ActivationCapabilityType capActivation = ResourceTypeUtil.getEffectiveCapability(accCtx.getResource(), ActivationCapabilityType.class); + ActivationCapabilityType capActivation = ResourceTypeUtil.getEffectiveCapability(projCtx.getResource(), ActivationCapabilityType.class); if (capActivation == null) { - LOGGER.trace("Skipping activation status and validity processing because {} has no activation capability", accCtx.getResource()); + LOGGER.trace("Skipping activation status and validity processing because {} has no activation capability", projCtx.getResource()); return; } @@ -259,43 +265,43 @@ public void processActivationUserCurrent(LensContext co ActivationLockoutStatusCapabilityType capLockoutStatus = CapabilityUtil.getEffectiveActivationLockoutStatus(capActivation); if (capStatus != null) { - evaluateActivationMapping(context, accCtx, + evaluateActivationMapping(context, projCtx, activationType.getAdministrativeStatus(), SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, capActivation, now, true, ActivationType.F_ADMINISTRATIVE_STATUS.getLocalPart(), task, result); } else { - LOGGER.trace("Skipping activation administrative status processing because {} does not have activation administrative status capability", accCtx.getResource()); + LOGGER.trace("Skipping activation administrative status processing because {} does not have activation administrative status capability", projCtx.getResource()); } ResourceBidirectionalMappingType validFromMappingType = activationType.getValidFrom(); if (validFromMappingType == null || validFromMappingType.getOutbound() == null) { - LOGGER.trace("Skipping activation validFrom processing because {} does not have appropriate outbound mapping", accCtx.getResource()); + LOGGER.trace("Skipping activation validFrom processing because {} does not have appropriate outbound mapping", projCtx.getResource()); } else if (capValidFrom == null && !ExpressionUtil.hasExplicitTarget(validFromMappingType.getOutbound())) { - LOGGER.trace("Skipping activation validFrom processing because {} does not have activation validFrom capability nor outbound mapping with explicit target", accCtx.getResource()); + LOGGER.trace("Skipping activation validFrom processing because {} does not have activation validFrom capability nor outbound mapping with explicit target", projCtx.getResource()); } else { - evaluateActivationMapping(context, accCtx, activationType.getValidFrom(), + evaluateActivationMapping(context, projCtx, activationType.getValidFrom(), SchemaConstants.PATH_ACTIVATION_VALID_FROM, SchemaConstants.PATH_ACTIVATION_VALID_FROM, null, now, true, ActivationType.F_VALID_FROM.getLocalPart(), task, result); } ResourceBidirectionalMappingType validToMappingType = activationType.getValidTo(); if (validToMappingType == null || validToMappingType.getOutbound() == null) { - LOGGER.trace("Skipping activation validTo processing because {} does not have appropriate outbound mapping", accCtx.getResource()); + LOGGER.trace("Skipping activation validTo processing because {} does not have appropriate outbound mapping", projCtx.getResource()); } else if (capValidTo == null && !ExpressionUtil.hasExplicitTarget(validToMappingType.getOutbound())) { - LOGGER.trace("Skipping activation validTo processing because {} does not have activation validTo capability nor outbound mapping with explicit target", accCtx.getResource()); + LOGGER.trace("Skipping activation validTo processing because {} does not have activation validTo capability nor outbound mapping with explicit target", projCtx.getResource()); } else { - evaluateActivationMapping(context, accCtx, activationType.getValidTo(), + evaluateActivationMapping(context, projCtx, activationType.getValidTo(), SchemaConstants.PATH_ACTIVATION_VALID_TO, SchemaConstants.PATH_ACTIVATION_VALID_TO, null, now, true, ActivationType.F_VALID_TO.getLocalPart(), task, result); } if (capLockoutStatus != null) { - evaluateActivationMapping(context, accCtx, + evaluateActivationMapping(context, projCtx, activationType.getLockoutStatus(), SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, capActivation, now, true, ActivationType.F_LOCKOUT_STATUS.getLocalPart(), task, result); } else { - LOGGER.trace("Skipping activation lockout status processing because {} does not have activation lockout status capability", accCtx.getResource()); + LOGGER.trace("Skipping activation lockout status processing because {} does not have activation lockout status capability", projCtx.getResource()); } } @@ -448,85 +454,75 @@ private boolean evaluateExistenceMapping(final LensContext // "default mapping" return legal; } - - MappingInitializer,PrismPropertyDefinition> initializer = new MappingInitializer,PrismPropertyDefinition>() { - @Override - public Mapping.Builder,PrismPropertyDefinition> initialize(Mapping.Builder,PrismPropertyDefinition> builder) throws SchemaException { - // Source: legal - ItemDeltaItem,PrismPropertyDefinition> legalSourceIdi = getLegalIdi(accCtx); - Source,PrismPropertyDefinition> legalSource - = new Source<>(legalSourceIdi, ExpressionConstants.VAR_LEGAL); - builder.setDefaultSource(legalSource); - - // Source: assigned - ItemDeltaItem,PrismPropertyDefinition> assignedIdi = getAssignedIdi(accCtx); - Source,PrismPropertyDefinition> assignedSource = new Source<>(assignedIdi, ExpressionConstants.VAR_ASSIGNED); - builder.addSource(assignedSource); - - // Source: focusExists - ItemDeltaItem,PrismPropertyDefinition> focusExistsSourceIdi = getFocusExistsIdi(context.getFocusContext()); - Source,PrismPropertyDefinition> focusExistsSource - = new Source<>(focusExistsSourceIdi, ExpressionConstants.VAR_FOCUS_EXISTS); - builder.addSource(focusExistsSource); - - // Variable: focus - builder.addVariableDefinition(ExpressionConstants.VAR_FOCUS, context.getFocusContext().getObjectDeltaObject()); - - // Variable: user (for convenience, same as "focus") - builder.addVariableDefinition(ExpressionConstants.VAR_USER, context.getFocusContext().getObjectDeltaObject()); - - // Variable: shadow - builder.addVariableDefinition(ExpressionConstants.VAR_SHADOW, accCtx.getObjectDeltaObject()); - - // Variable: resource - builder.addVariableDefinition(ExpressionConstants.VAR_RESOURCE, accCtx.getResource()); - - builder.setOriginType(OriginType.OUTBOUND); - builder.setOriginObject(accCtx.getResource()); - return builder; - } - }; - - final MutableBoolean output = new MutableBoolean(false); - - MappingOutputProcessor> processor = new MappingOutputProcessor>() { - @Override - public void process(ItemPath mappingOutputPath, - PrismValueDeltaSetTriple> outputTriple) throws ExpressionEvaluationException { - if (outputTriple == null) { - // The "default existence mapping" - output.setValue(legal); - return; - } - - Collection> nonNegativeValues = outputTriple.getNonNegativeValues(); - - // MID-3507: this is probably the bug. The processor is executed after every mapping. - // The processing will die on the error if one mapping returns a value and the other mapping returns null - // (e.g. because the condition is false). This should be fixed. - if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { - throw new ExpressionEvaluationException("Activation existence expression resulted in null or empty value for projection " + accCtxDesc); - } - if (nonNegativeValues.size() > 1) { - throw new ExpressionEvaluationException("Activation existence expression resulted in too many values ("+nonNegativeValues.size()+") for projection " + accCtxDesc); - } - - output.setValue(nonNegativeValues.iterator().next().getValue()); - } - }; - + MappingEvaluatorParams, PrismPropertyDefinition, ShadowType, F> params = new MappingEvaluatorParams<>(); params.setMappingTypes(outbound); params.setMappingDesc("outbound existence mapping in projection " + accCtxDesc); params.setNow(now); - params.setInitializer(initializer); - params.setProcessor(processor); params.setAPrioriTargetObject(accCtx.getObjectOld()); params.setEvaluateCurrent(current); params.setTargetContext(accCtx); params.setFixTarget(true); params.setContext(context); + params.setInitializer(builder -> { + // Source: legal + ItemDeltaItem,PrismPropertyDefinition> legalSourceIdi = getLegalIdi(accCtx); + Source,PrismPropertyDefinition> legalSource + = new Source<>(legalSourceIdi, ExpressionConstants.VAR_LEGAL); + builder.defaultSource(legalSource); + + // Source: assigned + ItemDeltaItem,PrismPropertyDefinition> assignedIdi = getAssignedIdi(accCtx); + Source,PrismPropertyDefinition> assignedSource = new Source<>(assignedIdi, ExpressionConstants.VAR_ASSIGNED); + builder.addSource(assignedSource); + + // Source: focusExists + ItemDeltaItem,PrismPropertyDefinition> focusExistsSourceIdi = getFocusExistsIdi(context.getFocusContext()); + Source,PrismPropertyDefinition> focusExistsSource + = new Source<>(focusExistsSourceIdi, ExpressionConstants.VAR_FOCUS_EXISTS); + builder.addSource(focusExistsSource); + + // Variable: focus + builder.addVariableDefinition(ExpressionConstants.VAR_FOCUS, context.getFocusContext().getObjectDeltaObject()); + + // Variable: user (for convenience, same as "focus") + builder.addVariableDefinition(ExpressionConstants.VAR_USER, context.getFocusContext().getObjectDeltaObject()); + + // Variable: shadow + builder.addVariableDefinition(ExpressionConstants.VAR_SHADOW, accCtx.getObjectDeltaObject()); + + // Variable: resource + builder.addVariableDefinition(ExpressionConstants.VAR_RESOURCE, accCtx.getResource()); + + builder.originType(OriginType.OUTBOUND); + builder.originObject(accCtx.getResource()); + return builder; + }); + + final MutableBoolean output = new MutableBoolean(false); + params.setProcessor((mappingOutputPath,outputTriple) -> { + if (outputTriple == null) { + // The "default existence mapping" + output.setValue(legal); + return; + } + + Collection> nonNegativeValues = outputTriple.getNonNegativeValues(); + + // MID-3507: this is probably the bug. The processor is executed after every mapping. + // The processing will die on the error if one mapping returns a value and the other mapping returns null + // (e.g. because the condition is false). This should be fixed. + if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { + throw new ExpressionEvaluationException("Activation existence expression resulted in null or empty value for projection " + accCtxDesc); + } + if (nonNegativeValues.size() > 1) { + throw new ExpressionEvaluationException("Activation existence expression resulted in too many values ("+nonNegativeValues.size()+") for projection " + accCtxDesc); + } + + output.setValue(nonNegativeValues.iterator().next().getValue()); + }); + PrismPropertyDefinitionImpl shadowExistsDef = new PrismPropertyDefinitionImpl<>( SHADOW_EXISTS_PROPERTY_NAME, DOMUtil.XSD_BOOLEAN, prismContext); @@ -534,10 +530,6 @@ public void process(ItemPath mappingOutputPath, shadowExistsDef.setMaxOccurs(1); params.setTargetItemDefinition(shadowExistsDef); mappingHelper.evaluateMappingSetProjection(params, task, result); - -// PrismValueDeltaSetTriple> outputTriple = mappingHelper.evaluateMappingSetProjection( -// outbound, "outbound existence mapping in projection " + accCtxDesc, -// now, initializer, null, null, accCtx.getObjectOld(), current, null, context, accCtx, task, result); return (boolean) output.getValue(); @@ -713,7 +705,76 @@ private ItemDeltaItem,PrismPr return new ItemDeltaItem,PrismPropertyDefinition>(existsPropOld, existsDelta, existsProp); } } - + + public void processLifecycle(LensContext context, LensProjectionContext projCtx, + XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null && !FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { + // We can do this only for focal object. + return; + } + + processLifecycleFocus((LensContext)context, projCtx, now, task, result); + } + + private void processLifecycleFocus(LensContext context, LensProjectionContext projCtx, + XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + return; + } + + ResourceObjectLifecycleDefinitionType lifecycleDef = null; + ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); + if (resourceAccountDefType != null) { + lifecycleDef = resourceAccountDefType.getLifecycle(); + } + ResourceBidirectionalMappingType lifecycleStateMappingType = null; + if (lifecycleDef != null) { + lifecycleStateMappingType = lifecycleDef.getLifecycleState(); + } + + if (lifecycleStateMappingType == null || lifecycleStateMappingType.getOutbound() == null) { + + if (!projCtx.isAdd()) { + return; + } + + PrismObject focusNew = focusContext.getObjectNew(); + if (focusNew == null) { + return; + } + + PrismObject projectionNew = projCtx.getObjectNew(); + if (projectionNew == null) { + return; + } + + String lifecycle = midpointFunctions.computeProjectionLifecycle( + focusNew.asObjectable(), projectionNew.asObjectable(), projCtx.getResource()); + + LOGGER.trace("Computed projection lifecycle (default expression): {}", lifecycle); + + if (lifecycle != null) { + PrismPropertyDefinition propDef = projCtx.getObjectDefinition().findPropertyDefinition(SchemaConstants.PATH_LIFECYCLE_STATE); + PropertyDelta lifeCycleDelta = propDef.createEmptyDelta(SchemaConstants.PATH_LIFECYCLE_STATE); + PrismPropertyValue pval = new PrismPropertyValue(lifecycle); + pval.setOriginType(OriginType.OUTBOUND); + lifeCycleDelta.setValuesToReplace(pval); + projCtx.swallowToSecondaryDelta(lifeCycleDelta); + } + + } else { + + evaluateActivationMapping(context, projCtx, lifecycleStateMappingType, + SchemaConstants.PATH_LIFECYCLE_STATE, SchemaConstants.PATH_LIFECYCLE_STATE, + null, now, false, ObjectType.F_LIFECYCLE_STATE.getLocalPart(), task, result); + } + + } + private PrismObjectDefinition getUserDefinition() { if (userDefinition == null) { userDefinition = prismContext.getSchemaRegistry() diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PasswordPolicyProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PasswordPolicyProcessor.java index 79d2137035a..8787e457c22 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PasswordPolicyProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PasswordPolicyProcessor.java @@ -32,7 +32,7 @@ import org.springframework.stereotype.Component; import com.evolveum.midpoint.model.common.stringpolicy.StringPolicyUtils; -import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; import com.evolveum.midpoint.model.impl.ModelObjectResolver; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensFocusContext; @@ -116,7 +116,7 @@ public class PasswordPolicyProcessor { Protector protector; @Autowired(required = true) - private ValuePolicyGenerator valuePolicyGenerator; + private ValuePolicyProcessor valuePolicyGenerator; @Autowired(required = true) ModelObjectResolver resolver; @@ -180,7 +180,7 @@ void processPasswordPolicy(LensFocusContext focusContex } ValuePolicyType passwordPolicy = determinePasswordPolicy(focusContext, task, result); - processPasswordPolicy(passwordPolicy, focusContext.getObjectOld(), null, passwordValueProperty, task, result); + processPasswordPolicy(focusContext, passwordPolicy, passwordValueProperty, task, result); if (passwordValueProperty != null && isPasswordChange) { processPasswordHistoryDeltas(focusContext, context, focusContext.getSecurityPolicy(), now, task, result); @@ -228,7 +228,7 @@ public ValuePolicyType determinePasswordPolicy(LensFocusC return passwordPolicy.asObjectable(); } - private void processPasswordPolicy(ValuePolicyType passwordPolicy, PrismObject focus, PrismObject projection, PrismProperty passwordProperty, + private void processPasswordPolicy(LensFocusContext focusContext, ValuePolicyType passwordPolicy, PrismProperty passwordProperty, Task task, OperationResult result) throws PolicyViolationException, SchemaException, ObjectNotFoundException, ExpressionEvaluationException { if (passwordPolicy == null) { @@ -237,14 +237,9 @@ private void processPasswordPolicy(ValuePolicyType passwor } String passwordValue = determinePasswordValue(passwordProperty); - PasswordType currentPasswordType = determineCurrentPassword(focus); + PasswordType currentPasswordType = determineCurrentPassword(focusContext.getObjectOld()); - boolean isValid; - if (projection != null) { - isValid = validatePassword(passwordValue, currentPasswordType, passwordPolicy, projection, "projection password policy", task, result); - } else { - isValid = validatePassword(passwordValue, currentPasswordType, passwordPolicy, focus, "focus password policy", task, result); - } + boolean isValid = validatePassword(passwordValue, currentPasswordType, passwordPolicy, focusContext.getObjectAny(), "focus password policy", task, result); if (!isValid) { result.computeStatus(); @@ -326,7 +321,19 @@ void processPasswordPolicy(LensProjectionContext projecti accountShadow = projectionContext.getObjectNew(); } - processPasswordPolicy(passwordPolicy, null, accountShadow, password, task, result); + if (passwordPolicy == null) { + LOGGER.trace("Skipping processing password policies. Password policy not specified."); + return; + } + + String passwordValue = determinePasswordValue(password); + + boolean isValid = validatePassword(passwordValue, null, passwordPolicy, accountShadow, "projection password policy", task, result); + + if (!isValid) { + result.computeStatus(); + throw new PolicyViolationException("Provided password does not satisfy password policies. " + result.getMessage()); + } } @@ -338,66 +345,16 @@ public boolean validatePassword(String newPassword, Passw OperationResult result = parentResult.createSubresult(OPERATION_PASSWORD_VALIDATION); result.addParam("policyName", pp.getName()); - normalize(pp); - - if (newPassword == null && - (pp.getMinOccurs() == null || XsdTypeMapper.multiplicityToInteger(pp.getMinOccurs()) == 0)) { - // No password is allowed - result.recordSuccess(); - return true; - } - - if (newPassword == null) { - newPassword = ""; - } - - LimitationsType lims = pp.getStringPolicy().getLimitations(); StringBuilder message = new StringBuilder(); - testMinimalLength(newPassword, lims, result, message); - testMaximalLength(newPassword, lims, result, message); - - testMinimalUniqueCharacters(newPassword, lims, result, message); + boolean stringPolicyValid = valuePolicyGenerator.validateValue(newPassword, pp, object, message, shortDesc, task, result); try { testPasswordHistoryEntries(newPassword, currentPasswordType, pp, result, message); } catch (EncryptionException e) { throw new SystemException(e.getMessage(), e); } - if (lims.getLimit() == null || lims.getLimit().isEmpty()) { - if (message.toString() == null || message.toString().isEmpty()) { - result.computeStatus(); - } else { - result.computeStatus(message.toString()); - - } - - return result.isAcceptable(); - } - - // check limitation - HashSet validChars = null; - HashSet allValidChars = new HashSet<>(); - List passwd = StringPolicyUtils.stringTokenizer(newPassword); - for (StringLimitType stringLimitationType : lims.getLimit()) { - OperationResult limitResult = new OperationResult( - "Tested limitation: " + stringLimitationType.getDescription()); - - validChars = getValidCharacters(stringLimitationType.getCharacterClass(), pp); - int count = countValidCharacters(validChars, passwd); - allValidChars.addAll(validChars); - testMinimalOccurence(stringLimitationType, count, limitResult, message); - testMaximalOccurence(stringLimitationType, count, limitResult, message); - testMustBeFirst(stringLimitationType, count, limitResult, message, newPassword, validChars); - - limitResult.computeStatus(); - result.addSubresult(limitResult); - } - testInvalidCharacters(passwd, allValidChars, result, message); - - testCheckExpression(newPassword, lims, object, shortDesc, task, result, message); - if (message.toString() == null || message.toString().isEmpty()) { result.computeStatus(); } else { @@ -405,33 +362,7 @@ public boolean validatePassword(String newPassword, Passw } - return result.isAcceptable(); - } - - /** - * add defined default values - */ - private void normalize(ValuePolicyType pp) { - if (null == pp) { - throw new IllegalArgumentException("Password policy cannot be null"); - } - - if (null == pp.getStringPolicy()) { - StringPolicyType sp = new StringPolicyType(); - pp.setStringPolicy(StringPolicyUtils.normalize(sp)); - } else { - pp.setStringPolicy(StringPolicyUtils.normalize(pp.getStringPolicy())); - } - - if (null == pp.getLifetime()) { - PasswordLifeTimeType lt = new PasswordLifeTimeType(); - lt.setExpiration(-1); - lt.setWarnBeforeExpiration(0); - lt.setLockAfterExpiration(0); - lt.setMinPasswordAge(0); - lt.setPasswordHistoryLength(0); - } - return; + return result.isAcceptable() && stringPolicyValid; } private void testPasswordHistoryEntries(String newPassword, PasswordType currentPasswordType, @@ -489,191 +420,6 @@ private void appendHistoryViolationMessage(OperationResult result, StringBuilder message.append("\n"); } - private void testInvalidCharacters(List password, HashSet validChars, - OperationResult result, StringBuilder message) { - - // Check if there is no invalid character - StringBuilder invalidCharacters = new StringBuilder(); - for (String s : password) { - if (!validChars.contains(s)) { - // memorize all invalid characters - invalidCharacters.append(s); - } - } - if (invalidCharacters.length() > 0) { - String msg = "Characters [ " + invalidCharacters + " ] are not allowed in password"; - result.addSubresult(new OperationResult("Check if password does not contain invalid characters", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); - } - // else { - // ret.addSubresult(new OperationResult("Check if password does not - // contain invalid characters OK.", - // OperationResultStatus.SUCCESS, "PASSED")); - // } - - } - - private void testMustBeFirst(StringLimitType stringLimitationType, int count, - OperationResult limitResult, StringBuilder message, String password, Set validChars) { - // test if first character is valid - if (stringLimitationType.isMustBeFirst() == null) { - stringLimitationType.setMustBeFirst(false); - } - // we check mustBeFirst only for non-empty passwords - if (StringUtils.isNotEmpty(password) && stringLimitationType.isMustBeFirst() - && !validChars.contains(password.substring(0, 1))) { - String msg = "First character is not from allowed set. Allowed set: " + validChars.toString(); - limitResult.addSubresult( - new OperationResult("Check valid first char", OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); - } - // else { - // limitResult.addSubresult(new OperationResult("Check valid first char - // in password OK.", - // OperationResultStatus.SUCCESS, "PASSED")); - // } - - } - - private void testMaximalOccurence(StringLimitType stringLimitationType, int count, - OperationResult limitResult, StringBuilder message) { - // Test maximal occurrence - if (stringLimitationType.getMaxOccurs() != null) { - - if (stringLimitationType.getMaxOccurs() < count) { - String msg = "Required maximal occurrence (" + stringLimitationType.getMaxOccurs() - + ") of characters (" + stringLimitationType.getDescription() - + ") in password was exceeded (occurrence of characters in password " + count + ")."; - limitResult.addSubresult(new OperationResult("Check maximal occurrence of characters", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); - } - // else { - // limitResult.addSubresult(new OperationResult( - // "Check maximal occurrence of characters in password OK.", - // OperationResultStatus.SUCCESS, - // "PASSED")); - // } - } - - } - - private void testMinimalOccurence(StringLimitType stringLimitation, int count, - OperationResult result, StringBuilder message) { - // Test minimal occurrence - if (stringLimitation.getMinOccurs() == null) { - stringLimitation.setMinOccurs(0); - } - if (stringLimitation.getMinOccurs() > count) { - String msg = "Required minimal occurrence (" + stringLimitation.getMinOccurs() - + ") of characters (" + stringLimitation.getDescription() - + ") in password is not met (occurrence of characters in password " + count + ")."; - result.addSubresult(new OperationResult("Check minimal occurrence of characters", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); - } - } - - private int countValidCharacters(Set validChars, List password) { - int count = 0; - for (String s : password) { - if (validChars.contains(s)) { - count++; - } - } - return count; - } - - private HashSet getValidCharacters(CharacterClassType characterClassType, - ValuePolicyType passwordPolicy) { - if (null != characterClassType.getValue()) { - return new HashSet(StringPolicyUtils.stringTokenizer(characterClassType.getValue())); - } else { - return new HashSet(StringPolicyUtils.stringTokenizer(StringPolicyUtils - .collectCharacterClass(passwordPolicy.getStringPolicy().getCharacterClass(), - characterClassType.getRef()))); - } - } - - private void testMinimalUniqueCharacters(String password, LimitationsType limitations, - OperationResult result, StringBuilder message) { - // Test uniqueness criteria - HashSet tmp = new HashSet(StringPolicyUtils.stringTokenizer(password)); - if (limitations.getMinUniqueChars() != null) { - if (limitations.getMinUniqueChars() > tmp.size()) { - String msg = "Required minimal count of unique characters (" + limitations.getMinUniqueChars() - + ") in password are not met (unique characters in password " + tmp.size() + ")"; - result.addSubresult(new OperationResult("Check minimal count of unique chars", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); - } - - } - } - - private void testMinimalLength(String password, LimitationsType limitations, - OperationResult result, StringBuilder message) { - // Test minimal length - if (limitations.getMinLength() == null) { - limitations.setMinLength(0); - } - if (limitations.getMinLength() > password.length()) { - String msg = "Required minimal size (" + limitations.getMinLength() - + ") of password is not met (password length: " + password.length() + ")"; - result.addSubresult(new OperationResult("Check global minimal length", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); - } - } - - private void testMaximalLength(String password, LimitationsType limitations, - OperationResult result, StringBuilder message) { - // Test maximal length - if (limitations.getMaxLength() != null) { - if (limitations.getMaxLength() < password.length()) { - String msg = "Required maximal size (" + limitations.getMaxLength() - + ") of password was exceeded (password length: " + password.length() + ")."; - result.addSubresult(new OperationResult("Check global maximal length", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); - } - } - } - - private void testCheckExpression(String newPassword, LimitationsType lims, PrismObject object, - String shortDesc, Task task, OperationResult result, StringBuilder message) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { - - List checkExpressions = lims.getCheckExpression(); - if (checkExpressions.isEmpty()) { - return; - } - for (CheckExpressionType checkExpression: checkExpressions) { - ExpressionType expressionType = checkExpression.getExpression(); - if (expressionType == null) { - return; - } - if (!valuePolicyGenerator.checkExpression(newPassword, expressionType, object, shortDesc, task, result)) { - String msg = checkExpression.getFailureMessage(); - if (msg == null) { - msg = "Check expression failed"; - } - result.addSubresult(new OperationResult("Check expression", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); - } - } - - } - private boolean passwordEquals(ProtectedStringType newPasswordPs, ProtectedStringType currentPassword) throws SchemaException, EncryptionException { if (currentPassword == null) { return newPasswordPs == null; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java index 312f0883d04..8187ee2b324 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java @@ -191,7 +191,7 @@ private void projectInternal(LensContext context, Stri context.setFresh(true); if (consistencyChecks) context.checkConsistence(); }, - partialProcessingOptions::getLoad); + partialProcessingOptions::getLoad, result); } // For now let's pretend to do just one wave. The maxWaves number will be corrected in the // first wave when dependencies are sorted out for the first time. @@ -225,7 +225,7 @@ private void projectInternal(LensContext context, Stri context.recomputeFocus(); if (consistencyChecks) context.checkConsistence(); }, - partialProcessingOptions::getFocus); + partialProcessingOptions::getFocus, result); LensUtil.traceContext(LOGGER, activityDescription, "focus processing", false, context, false); LensUtil.checkContextSanity(context, "focus processing", result); @@ -343,7 +343,6 @@ private void projectProjection(LensContext context, Le if (consistencyChecks) context.checkConsistence(); projectionContext.recompute(); - //SynchronizerUtil.traceContext("values", context, false); if (consistencyChecks) context.checkConsistence(); }, partialProcessingOptions::getProjectionValues); @@ -352,7 +351,6 @@ private void projectProjection(LensContext context, Le LensUtil.partialExecute("projectionCredentials", () -> { credentialsProcessor.processProjectionCredentials(context, projectionContext, now, task, result); - //SynchronizerUtil.traceContext("credentials", context, false); if (consistencyChecks) context.checkConsistence(); projectionContext.recompute(); @@ -372,6 +370,17 @@ private void projectProjection(LensContext context, Le partialProcessingOptions::getProjectionReconciliation); + LensUtil.partialExecute("projectionLifecycle", + () -> { + activationProcessor.processLifecycle(context, projectionContext, now, task, result); + if (consistencyChecks) context.checkConsistence(); + + projectionContext.recompute(); + LensUtil.traceContext(LOGGER, activityDescription, "projection lifecycle of "+projectionDesc, false, context, false); + if (consistencyChecks) context.checkConsistence(); + }, + partialProcessingOptions::getProjectionLifecycle); + result.recordSuccess(); } catch (ObjectNotFoundException | CommunicationException | SchemaException | ConfigurationException | SecurityViolationException diff --git a/model/model-impl/src/main/resources/ctx-model.xml b/model/model-impl/src/main/resources/ctx-model.xml index 431728b39ef..85373f8d3c9 100644 --- a/model/model-impl/src/main/resources/ctx-model.xml +++ b/model/model-impl/src/main/resources/ctx-model.xml @@ -143,7 +143,7 @@ scope="singleton"> - + @@ -217,6 +217,9 @@ + + + diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/AbstractModelImplementationIntegrationTest.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/AbstractModelImplementationIntegrationTest.java index c6238ae938e..a606eb8e680 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/AbstractModelImplementationIntegrationTest.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/AbstractModelImplementationIntegrationTest.java @@ -38,6 +38,7 @@ import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ObjectModificationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.jetbrains.annotations.NotNull; import javax.xml.bind.JAXBException; import javax.xml.namespace.QName; @@ -46,6 +47,7 @@ import java.io.IOException; import java.util.function.Consumer; +import static com.evolveum.midpoint.test.IntegrationTestTools.display; import static org.testng.AssertJUnit.*; /** @@ -317,4 +319,27 @@ protected void assertUserSecondaryDelta(LensContext context) throws Sc assertFalse("User secondary delta is empty", userSecondaryDelta.isEmpty()); } + @NotNull + protected LensContext createContextForRoleAssignment(String userOid, String roleOid, QName relation, + Consumer modificationBlock, OperationResult result) + throws SchemaException, ObjectNotFoundException, JAXBException { + return createContextForAssignment(UserType.class, userOid, RoleType.class, roleOid, relation, modificationBlock, result); + } + + @NotNull + protected LensContext createContextForAssignment(Class focusClass, String focusOid, + Class targetClass, String targetOid, QName relation, + Consumer modificationBlock, OperationResult result) + throws SchemaException, ObjectNotFoundException, JAXBException { + LensContext context = createLensContext(focusClass); + fillContextWithFocus(context, focusClass, focusOid, result); + QName targetType = prismContext.getSchemaRegistry().determineTypeForClass(targetClass); + assertNotNull("Unknown target class "+targetClass, targetType); + addFocusDeltaToContext(context, createAssignmentFocusDelta(focusClass, focusOid, targetOid, targetType, relation, + modificationBlock, true)); + context.recompute(); + display("Input context", context); + assertFocusModificationSanity(context); + return context; + } } diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java index 38cfc3b6588..ba3bdbb4691 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java @@ -30,20 +30,18 @@ import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.test.util.TestUtil; -import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; +import org.apache.commons.collections4.Bag; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.bag.TreeBag; import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.testng.annotations.Test; -import javax.xml.bind.JAXBException; -import javax.xml.namespace.QName; import java.io.File; import java.util.*; -import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -51,8 +49,6 @@ import static com.evolveum.midpoint.test.IntegrationTestTools.displayObjectTypeCollection; import static com.evolveum.midpoint.test.util.TestUtil.assertSuccess; import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertNotNull; -import static org.testng.AssertJUnit.fail; /** * Comprehensive test of assignment evaluator and processor. @@ -87,26 +83,38 @@ * * @author mederly */ +@SuppressWarnings({ "FieldCanBeLocal", "SameParameterValue" }) public class TestAssignmentProcessor2 extends AbstractLensTest { private static final int CONSTRUCTION_LEVELS = 5; private static final int FOCUS_MAPPING_LEVELS = 5; private static final int POLICY_RULES_LEVELS = 5; - public static final File RESOURCE_DUMMY_EMPTY_FILE = new File(TEST_DIR, "resource-dummy-empty.xml"); - public static final String RESOURCE_DUMMY_EMPTY_OID = "10000000-0000-0000-0000-00000000EEE4"; - public static final String RESOURCE_DUMMY_EMPTY_INSTANCE_NAME = "empty"; + private static final boolean FIRST_PART = true; + private static final boolean SECOND_PART = true; + + private static final File RESOURCE_DUMMY_EMPTY_FILE = new File(TEST_DIR, "resource-dummy-empty.xml"); + private static final String RESOURCE_DUMMY_EMPTY_OID = "10000000-0000-0000-0000-00000000EEE4"; + private static final String RESOURCE_DUMMY_EMPTY_INSTANCE_NAME = "empty"; @Autowired private AssignmentProcessor assignmentProcessor; @Autowired private Clock clock; + // first part private RoleType role1, role2, role4, role5, role6; private OrgType org3; private RoleType metarole1, metarole2, metarole3, metarole4; private RoleType metametarole1; + + // second part + private RoleType role7, role8, role9; + private RoleType metarole7, metarole8, metarole9; + private RoleType metametarole7; + private List objects; private static final String ROLE_R1_OID = getRoleOid("R1"); + private static final String ROLE_R7_OID = getRoleOid("R7"); private static final String ROLE_MR1_OID = getRoleOid("MR1"); @Override @@ -115,10 +123,10 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti initDummyResourcePirate(RESOURCE_DUMMY_EMPTY_INSTANCE_NAME, RESOURCE_DUMMY_EMPTY_FILE, RESOURCE_DUMMY_EMPTY_OID, initTask, initResult); - createObjects(false, initTask, initResult, null); + createObjectsInFirstPart(false, initTask, initResult, null); } - @Test + @Test(enabled = FIRST_PART) public void test010AssignR1ToJack() throws Exception { final String TEST_NAME = "test010AssignR1ToJack"; TestUtil.displayTestTile(this, TEST_NAME); @@ -139,7 +147,8 @@ public void test010AssignR1ToJack() throws Exception { result.computeStatus(); assertSuccess("Assignment processor failed (result)", result); - Collection evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); + Collection> evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); + @SuppressWarnings({ "unchecked", "raw" }) EvaluatedAssignmentImpl evaluatedAssignment = evaluatedAssignments.iterator().next(); assertEquals("Wrong evaluatedAssignment.isValid", true, evaluatedAssignment.isValid()); @@ -162,7 +171,7 @@ public void test010AssignR1ToJack() throws Exception { assertGuiConfig(evaluatedAssignment, "R1 R2 O3 R4 R5 R6"); } - @Test + @Test(enabled = FIRST_PART) public void test020AssignMR1ToR1() throws Exception { final String TEST_NAME = "test020AssignMR1ToR1"; TestUtil.displayTestTile(this, TEST_NAME); @@ -187,11 +196,11 @@ public void test020AssignMR1ToR1() throws Exception { // assignment of focus mappings R1-0 // assignment of focus policy rules R1-0 // assignment of metarole MR1 (this will be checked) - Collection evaluatedAssignments = assertAssignmentTripleSetSize(context, 4, 0, 0); - List targetedAssignments = evaluatedAssignments.stream().filter(ea -> ea.getTarget() != null) + Collection> evaluatedAssignments = assertAssignmentTripleSetSize(context, 4, 0, 0); + List> targetedAssignments = evaluatedAssignments.stream().filter(ea -> ea.getTarget() != null) .collect(Collectors.toList()); assertEquals("Wrong # of targeted assignments", 1, targetedAssignments.size()); - EvaluatedAssignmentImpl evaluatedAssignment = targetedAssignments.get(0); + EvaluatedAssignmentImpl evaluatedAssignment = targetedAssignments.get(0); assertEquals("Wrong evaluatedAssignment.isValid", true, evaluatedAssignment.isValid()); @@ -211,7 +220,7 @@ public void test020AssignMR1ToR1() throws Exception { assertGuiConfig(evaluatedAssignment, "MR1 MR3 MR4"); } - @Test + @Test(enabled = FIRST_PART) public void test030AssignR1ToJackProjectorDisabled() throws Exception { final String TEST_NAME = "test030AssignR1ToJackProjectorDisabled"; TestUtil.displayTestTile(this, TEST_NAME); @@ -243,7 +252,7 @@ public void test030AssignR1ToJackProjectorDisabled() throws Exception { * As R1 is assigned with the relation=approver, jack will "see" only this role. * However, we must collect all relevant target policy rules. */ - @Test + @Test(enabled = FIRST_PART) public void test040AssignR1ToJackAsApprover() throws Exception { final String TEST_NAME = "test040AssignR1ToJackAsApprover"; TestUtil.displayTestTile(this, TEST_NAME); @@ -264,7 +273,7 @@ public void test040AssignR1ToJackAsApprover() throws Exception { result.computeStatus(); assertSuccess("Assignment processor failed (result)", result); - Collection evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); + Collection> evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); EvaluatedAssignmentImpl evaluatedAssignment = evaluatedAssignments.iterator().next(); assertEquals("Wrong evaluatedAssignment.isValid", true, evaluatedAssignment.isValid()); @@ -300,7 +309,7 @@ public void test040AssignR1ToJackAsApprover() throws Exception { * (D = deputy assignment) * */ - @Test + @Test(enabled = FIRST_PART) public void test050JackDeputyOfBarbossa() throws Exception { final String TEST_NAME = "test050JackDeputyOfBarbossa"; TestUtil.displayTestTile(this, TEST_NAME); @@ -337,7 +346,7 @@ public void test050JackDeputyOfBarbossa() throws Exception { result.computeStatus(); assertSuccess("Assignment processor failed (result)", result); - Collection evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); + Collection> evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); EvaluatedAssignmentImpl evaluatedAssignment = evaluatedAssignments.iterator().next(); assertEquals("Wrong evaluatedAssignment.isValid", true, evaluatedAssignment.isValid()); @@ -383,7 +392,7 @@ public void test050JackDeputyOfBarbossa() throws Exception { * (D = deputy assignment) * */ - @Test + @Test(enabled = FIRST_PART) public void test060JackDeputyOfGuybrushDeputyOfBarbossa() throws Exception { final String TEST_NAME = "test060JackDeputyOfGuybrushDeputyOfBarbossa"; TestUtil.displayTestTile(this, TEST_NAME); @@ -420,7 +429,7 @@ public void test060JackDeputyOfGuybrushDeputyOfBarbossa() throws Exception { result.computeStatus(); assertSuccess("Assignment processor failed (result)", result); - Collection evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); + Collection> evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); EvaluatedAssignmentImpl evaluatedAssignment = evaluatedAssignments.iterator().next(); assertEquals("Wrong evaluatedAssignment.isValid", true, evaluatedAssignment.isValid()); @@ -468,7 +477,7 @@ public void test060JackDeputyOfGuybrushDeputyOfBarbossa() throws Exception { * jack */ - @Test + @Test(enabled = FIRST_PART) public void test100DisableSomeRoles() throws Exception { final String TEST_NAME = "test100DisableSomeRoles"; TestUtil.displayTestTile(this, TEST_NAME); @@ -478,14 +487,14 @@ public void test100DisableSomeRoles() throws Exception { OperationResult result = task.getResult(); // WHEN - createObjects(true, task, result, () -> disableRoles("MMR1 R2 MR3 R4")); + createObjectsInFirstPart(true, task, result, () -> disableRoles("MMR1 R2 MR3 R4")); // THEN // TODO check e.g. membershipRef for roles } - @Test + @Test(enabled = FIRST_PART) public void test110AssignR1ToJack() throws Exception { final String TEST_NAME = "test010AssignR1ToJack"; TestUtil.displayTestTile(this, TEST_NAME); @@ -506,7 +515,7 @@ public void test110AssignR1ToJack() throws Exception { result.computeStatus(); assertSuccess("Assignment processor failed (result)", result); - Collection evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); + Collection> evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); EvaluatedAssignmentImpl evaluatedAssignment = evaluatedAssignments.iterator().next(); assertEquals("Wrong evaluatedAssignment.isValid", true, evaluatedAssignment.isValid()); @@ -546,7 +555,7 @@ public void test110AssignR1ToJack() throws Exception { * jack */ - @Test + @Test(enabled = FIRST_PART) public void test150DisableSomeAssignments() throws Exception { final String TEST_NAME = "test150DisableSomeAssignments"; TestUtil.displayTestTile(this, TEST_NAME); @@ -556,12 +565,12 @@ public void test150DisableSomeAssignments() throws Exception { OperationResult result = task.getResult(); // WHEN - createObjects(true, task, result, () -> disableAssignments("MR4-R6 MR1-MR3 R1-R2")); + createObjectsInFirstPart(true, task, result, () -> disableAssignments("MR4-R6 MR1-MR3 R1-R2")); // THEN } - @Test + @Test(enabled = FIRST_PART) public void test160AssignR1ToJack() throws Exception { final String TEST_NAME = "test160AssignR1ToJack"; TestUtil.displayTestTile(this, TEST_NAME); @@ -582,7 +591,7 @@ public void test160AssignR1ToJack() throws Exception { result.computeStatus(); assertSuccess("Assignment processor failed (result)", result); - Collection evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); + Collection> evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); EvaluatedAssignmentImpl evaluatedAssignment = evaluatedAssignments.iterator().next(); assertEquals("Wrong evaluatedAssignment.isValid", true, evaluatedAssignment.isValid()); @@ -622,7 +631,7 @@ public void test160AssignR1ToJack() throws Exception { * jack */ - @Test + @Test(enabled = FIRST_PART) public void test200AddConditions() throws Exception { final String TEST_NAME = "test200AddConditions"; TestUtil.displayTestTile(this, TEST_NAME); @@ -632,7 +641,7 @@ public void test200AddConditions() throws Exception { OperationResult result = task.getResult(); // WHEN - createObjects(true, task, result, () -> { + createObjectsInFirstPart(true, task, result, () -> { disableRoles("R4"); addConditionToRoles("MR1+ MR30 MR4-"); addConditionToAssignments("R1-MR1+ MR1-MMR1+ R1-R2- MR2-O3+"); @@ -643,7 +652,7 @@ public void test200AddConditions() throws Exception { } - @Test + @Test(enabled = FIRST_PART) public void test210AssignR1ToJack() throws Exception { final String TEST_NAME = "test210AssignR1ToJack"; TestUtil.displayTestTile(this, TEST_NAME); @@ -668,7 +677,7 @@ public void test210AssignR1ToJack() throws Exception { result.computeStatus(); assertSuccess("Assignment processor failed (result)", result); - Collection evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); + Collection> evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); EvaluatedAssignmentImpl evaluatedAssignment = evaluatedAssignments.iterator().next(); assertEquals("Wrong evaluatedAssignment.isValid", true, evaluatedAssignment.isValid()); @@ -689,9 +698,150 @@ public void test210AssignR1ToJack() throws Exception { assertGuiConfig(evaluatedAssignment, "R1"); } + /** + * Testing targets with multiple incoming paths. + * + * MMR7 -------I--------* + * ^^ | + * || | + * |+--------+ | + * | | V + * MR7 MR8 MR9 + * ^ ^ | + * | | | + * | | V + * R7 --I--> R8 R9 + * ^ + * | + * | + * jack + * + */ + + @Test(enabled = SECOND_PART) + public void test300AssignR7ToJack() throws Exception { + final String TEST_NAME = "test300AssignR7ToJack"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(TestAssignmentProcessor.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + createObjectsInSecondPart(false, task, result, null); + + LensContext context = createContextForRoleAssignment(USER_JACK_OID, ROLE_R7_OID, null, null, result); + + // WHEN + assignmentProcessor.processAssignmentsProjections(context, clock.currentTimeXMLGregorianCalendar(), task, result); + + // THEN + display("Output context", context); + display("Evaluated assignment triple", context.getEvaluatedAssignmentTriple()); + + result.computeStatus(); + assertSuccess("Assignment processor failed (result)", result); + + Collection> evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); + EvaluatedAssignmentImpl evaluatedAssignment = evaluatedAssignments.iterator().next(); + assertEquals("Wrong evaluatedAssignment.isValid", true, evaluatedAssignment.isValid()); + + /* We expect some duplicates, namely: + + Constructions: + DeltaSetTriple: + zero: + description: R7-1 + description: R8-1 + description: R9-1 description: R9-1 + description: MR7-2 + description: MR8-2 + description: MR9-2 description: MR9-2 + description: MMR7-3 description: MMR7-3 + plus: + minus: + Roles: + DeltaSetTriple: + zero: + EvaluatedAssignmentTarget: + name: R7 + name: R8 + name: R9 name: R9 + name: MR7 + name: MR8 + name: MR9 name: MR9 + name: MMR7 name: MMR7 + plus: + minus: + Membership: + PRV(object=role:99999999-0000-0000-0000-0000000000R7(R7)) + PRV(object=role:99999999-0000-0000-0000-0000000000R8(R8)) + PRV(object=role:99999999-0000-0000-0000-0000000000R9(R9)) PRV(object=role:99999999-0000-0000-0000-0000000000R9(R9)) + Authorizations: + [R7]) + [R8]) + [R9]) [R9]) + Focus Mappings: + M(R7-1: {...common/common-3}description = PVDeltaSetTriple(zero: [PPV(String:jack)]; plus: []; minus: []; )) + M(R8-1: {...common/common-3}description = PVDeltaSetTriple(zero: [PPV(String:jack)]; plus: []; minus: []; )) + M(R9-1: {...common/common-3}description = PVDeltaSetTriple(zero: [PPV(String:jack)]; plus: []; minus: []; )) M(R9-1: {...common/common-3}description = PVDeltaSetTriple(zero: [PPV(String:jack)]; plus: []; minus: []; )) + M(MR7-2: {...common/common-3}description = PVDeltaSetTriple(zero: [PPV(String:jack)]; plus: []; minus: []; )) + M(MR8-2: {...common/common-3}description = PVDeltaSetTriple(zero: [PPV(String:jack)]; plus: []; minus: []; )) + M(MR9-2: {...common/common-3}description = PVDeltaSetTriple(zero: [PPV(String:jack)]; plus: []; minus: []; )) M(MR9-2: {...common/common-3}description = PVDeltaSetTriple(zero: [PPV(String:jack)]; plus: []; minus: []; )) + M(MMR7-3: {...common/common-3}description = PVDeltaSetTriple(zero: [PPV(String:jack)]; plus: []; minus: []; )) M(MMR7-3: {...common/common-3}description = PVDeltaSetTriple(zero: [PPV(String:jack)]; plus: []; minus: []; )) + Target: role:99999999-0000-0000-0000-0000000000R7(R7) + focusPolicyRules: + [ + name: R7-1 + name: R8-1 + name: R9-1 name: R9-1 + name: MR7-2 + name: MR8-2 + name: MR9-2 name: MR9-2 + name: MMR7-3 name: MMR7-3 + ] + thisTargetPolicyRules: + [ + name: R7-0 + name: MR7-1 + name: MMR7-2 + name: MR9-1 + ] + otherTargetsPolicyRules: + [ + name: R8-0 + name: R9-0 name: R9-0 + name: MR8-1 + name: MR9-1 + name: MMR7-2 + ] + + For all path-sensitive items (constructions, targets, focus mappings, policy rules) it is OK, because the items will + differ in assignment path; this might be relevant (e.g. w.r.t. validity, or further processing of e.g. constructions + or policy rules). + + Simple "scalar" results, like membership, authorizations or gui config, should not contain duplicates. + */ + + assertTargets(evaluatedAssignment, true, "R7 R8 R9 R9", null, null, null, null, null); + assertTargets(evaluatedAssignment, false, "MR7 MR8 MR9 MR9 MMR7 MMR7", null, null, null, null, null); + assertMembershipRef(evaluatedAssignment, "R7 R8 R9"); + assertOrgRef(evaluatedAssignment, ""); + assertDelegation(evaluatedAssignment, ""); + + String expectedItems = "R7-1 R8-1 R9-1 R9-1 MR7-2 MR8-2 MR9-2 MR9-2 MMR7-3 MMR7-3"; + assertConstructions(evaluatedAssignment, expectedItems, null, null, null, null, null); + assertFocusMappings(evaluatedAssignment, expectedItems); + assertFocusPolicyRules(evaluatedAssignment, expectedItems); + + assertTargetPolicyRules(evaluatedAssignment, + "R7-0 MR7-1 MMR7-2 MR9-1", + "R8-0 R9-0 R9-0 MR8-1 MR9-1 MMR7-2"); + assertAuthorizations(evaluatedAssignment, "R7 R8 R9"); + assertGuiConfig(evaluatedAssignment, "R7 R8 R9"); + } //region ============================================================= helper methods (preparing scenarios) - private void createObjects(boolean deleteFirst, Task task, OperationResult result, Runnable adjustment) throws Exception { + private void createObjectsInFirstPart(boolean deleteFirst, Task task, OperationResult result, Runnable adjustment) throws Exception { role1 = createRole(1, 1); role1.setDelegable(true); role2 = createRole(1, 2); @@ -718,6 +868,33 @@ private void createObjects(boolean deleteFirst, Task task, OperationResult resul objects = new ArrayList<>( Arrays.asList(role1, role2, org3, role4, role5, role6, metarole1, metarole2, metarole3, metarole4, metametarole1)); + createObjects(deleteFirst, task, result, adjustment); + } + + private void createObjectsInSecondPart(boolean deleteFirst, Task task, OperationResult result, Runnable adjustment) throws Exception { + role7 = createRole(1, 7); + role7.setDelegable(true); + role8 = createRole(1, 8); + role9 = createRole(1, 9); + metarole7 = createRole(2, 7); + metarole8 = createRole(2, 8); + metarole9 = createRole(2, 9); + metametarole7 = createRole(3, 7); + assign(role7, metarole7); + assign(role8, metarole8); + assign(metarole7, metametarole7); + assign(metarole8, metametarole7); + induce(role7, role8, 1); + induce(metametarole7, metarole9, 2); + induce(metarole9, role9, 2); + + objects = new ArrayList<>( + Arrays.asList(role7, role8, role9, metarole7, metarole8, metarole9, metametarole7)); + + createObjects(deleteFirst, task, result, adjustment); + } + + private void createObjects(boolean deleteFirst, Task task, OperationResult result, Runnable adjustment) throws Exception { if (adjustment != null) { adjustment.run(); } @@ -882,30 +1059,6 @@ private static String getRoleOid(String name) { return "99999999-0000-0000-0000-" + StringUtils.repeat('0', 12-name.length()) + name; } - @NotNull - private LensContext createContextForRoleAssignment(String userOid, String roleOid, QName relation, - Consumer modificationBlock, OperationResult result) - throws SchemaException, ObjectNotFoundException, JAXBException { - return createContextForAssignment(UserType.class, userOid, RoleType.class, roleOid, relation, modificationBlock, result); - } - - @NotNull - protected LensContext createContextForAssignment(Class focusClass, String focusOid, - Class targetClass, String targetOid, QName relation, - Consumer modificationBlock, OperationResult result) - throws SchemaException, ObjectNotFoundException, JAXBException { - LensContext context = createLensContext(focusClass); - fillContextWithFocus(context, focusClass, focusOid, result); - QName targetType = prismContext.getSchemaRegistry().determineTypeForClass(targetClass); - assertNotNull("Unknown target class "+targetClass, targetType); - addFocusDeltaToContext(context, createAssignmentFocusDelta(focusClass, focusOid, targetOid, targetType, relation, - modificationBlock, true)); - context.recompute(); - display("Input context", context); - assertFocusModificationSanity(context); - return context; - } - //endregion //region ============================================================= helper methods (asserts) @@ -922,17 +1075,18 @@ private void assertOrgRef(EvaluatedAssignmentImpl evaluated } private void assertAuthorizations(EvaluatedAssignmentImpl evaluatedAssignment, String text) { - List expected = getList(text); - assertEquals("Wrong # of authorizations", expected.size(), evaluatedAssignment.getAuthorizations().size()); - assertEquals("Wrong authorizations", new HashSet<>(expected), - evaluatedAssignment.getAuthorizations().stream().map(a -> a.getAction().get(0)).collect(Collectors.toSet())); + assertUnsortedListsEquals("Wrong authorizations", getList(text), evaluatedAssignment.getAuthorizations(), a -> a.getAction().get(0)); } private void assertGuiConfig(EvaluatedAssignmentImpl evaluatedAssignment, String text) { - List expected = getList(text); - assertEquals("Wrong # of gui configurations", expected.size(), evaluatedAssignment.getAdminGuiConfigurations().size()); - assertEquals("Wrong gui authorizations", new HashSet<>(expected), - evaluatedAssignment.getAdminGuiConfigurations().stream().map(g -> g.getPreferredDataLanguage()).collect(Collectors.toSet())); + assertUnsortedListsEquals("Wrong gui configurations", getList(text), + evaluatedAssignment.getAdminGuiConfigurations(), g -> g.getPreferredDataLanguage()); + } + + private void assertUnsortedListsEquals(String message, Collection expected, Collection real, Function nameExtractor) { + Bag expectedAsBag = new TreeBag<>(CollectionUtils.emptyIfNull(expected)); + Bag realAsBag = new TreeBag<>(real.stream().map(nameExtractor).collect(Collectors.toList())); + assertEquals(message, expectedAsBag, realAsBag); } private void assertFocusMappings(EvaluatedAssignmentImpl evaluatedAssignment, String expectedItems) { @@ -940,10 +1094,7 @@ private void assertFocusMappings(EvaluatedAssignmentImpl ev } private void assertFocusMappings(EvaluatedAssignmentImpl evaluatedAssignment, Collection expectedItems) { - expectedItems = CollectionUtils.emptyIfNull(expectedItems); - assertEquals("Wrong # of focus mappings", expectedItems.size(), evaluatedAssignment.getFocusMappings().size()); - assertEquals("Wrong focus mappings", new HashSet<>(expectedItems), - evaluatedAssignment.getFocusMappings().stream().map(m -> m.getMappingType().getName()).collect(Collectors.toSet())); + assertUnsortedListsEquals("Wrong focus mappings", expectedItems, evaluatedAssignment.getFocusMappings(), m -> m.getMappingType().getName()); // TODO look at the content of the mappings (e.g. zero, plus, minus sets) } @@ -952,10 +1103,7 @@ private void assertFocusPolicyRules(EvaluatedAssignmentImpl } private void assertFocusPolicyRules(EvaluatedAssignmentImpl evaluatedAssignment, Collection expectedItems) { - expectedItems = CollectionUtils.emptyIfNull(expectedItems); - assertEquals("Wrong # of focus policy rules", expectedItems.size(), evaluatedAssignment.getFocusPolicyRules().size()); - assertEquals("Wrong focus policy rules", new HashSet<>(expectedItems), - evaluatedAssignment.getFocusPolicyRules().stream().map(r -> r.getName()).collect(Collectors.toSet())); + assertUnsortedListsEquals("Wrong focus policy rules", expectedItems, evaluatedAssignment.getFocusPolicyRules(), r -> r.getName()); } private void assertTargetPolicyRules(EvaluatedAssignmentImpl evaluatedAssignment, @@ -967,12 +1115,10 @@ private void assertTargetPolicyRules(EvaluatedAssignmentImpl expectedThisTargetItems, Collection expectedOtherTargetsItems) { expectedOtherTargetsItems = CollectionUtils.emptyIfNull(expectedOtherTargetsItems); expectedThisTargetItems = CollectionUtils.emptyIfNull(expectedThisTargetItems); - assertEquals("Wrong # of other targets policy rules", expectedOtherTargetsItems.size(), evaluatedAssignment.getOtherTargetsPolicyRules().size()); - assertEquals("Wrong # of this target policy rules", expectedThisTargetItems.size(), evaluatedAssignment.getThisTargetPolicyRules().size()); - assertEquals("Wrong other targets policy rules", new HashSet<>(expectedOtherTargetsItems), - evaluatedAssignment.getOtherTargetsPolicyRules().stream().map(r -> r.getName()).collect(Collectors.toSet())); - assertEquals("Wrong this target policy rules", new HashSet<>(expectedThisTargetItems), - evaluatedAssignment.getThisTargetPolicyRules().stream().map(r -> r.getName()).collect(Collectors.toSet())); + assertUnsortedListsEquals("Wrong other targets policy rules", expectedOtherTargetsItems, + evaluatedAssignment.getOtherTargetsPolicyRules(), r -> r.getName()); + assertUnsortedListsEquals("Wrong this target policy rules", expectedThisTargetItems, + evaluatedAssignment.getThisTargetPolicyRules(), r -> r.getName()); } private void assertTargets(EvaluatedAssignmentImpl evaluatedAssignment, @@ -1002,12 +1148,10 @@ private void assertTargets(String type, Collection realInvalid = targets.stream() .filter(t -> !t.isValid() && matchesConstructions(t, evaluateConstructions)).collect(Collectors.toList()); String ec = evaluateConstructions != null ? " (evaluateConstructions: " + evaluateConstructions + ")" : ""; - assertEquals("Wrong # of valid targets in " + type + " set" + ec, expectedValid.size(), realValid.size()); - assertEquals("Wrong # of invalid targets in " + type + " set" + ec, expectedInvalid.size(), realInvalid.size()); - assertEquals("Wrong valid targets in " + type + " set" + ec, new HashSet<>(expectedValid), - realValid.stream().map(t -> t.getTarget().getName().getOrig()).collect(Collectors.toSet())); - assertEquals("Wrong invalid targets in " + type + " set" + ec, new HashSet<>(expectedInvalid), - realInvalid.stream().map(t -> t.getTarget().getName().getOrig()).collect(Collectors.toSet())); + assertUnsortedListsEquals("Wrong valid targets in " + type + " set" + ec, expectedValid, + realValid, t -> t.getTarget().getName().getOrig()); + assertUnsortedListsEquals("Wrong invalid targets in " + type + " set" + ec, expectedInvalid, + realInvalid, t -> t.getTarget().getName().getOrig()); } private boolean matchesConstructions(EvaluatedAssignmentTargetImpl t, Boolean evaluateConstructions) { @@ -1038,19 +1182,18 @@ private void assertConstructions(String type, Collection expectedInvalid = CollectionUtils.emptyIfNull(invalid0); Collection> realValid = constructions.stream().filter(c -> c.isValid()).collect(Collectors.toList()); Collection> realInvalid = constructions.stream().filter(c -> !c.isValid()).collect(Collectors.toList()); - assertEquals("Wrong # of valid constructions in " + type + " set", expectedValid.size(), realValid.size()); - assertEquals("Wrong # of invalid constructions in " + type + " set", expectedInvalid.size(), realInvalid.size()); - assertEquals("Wrong valid constructions in " + type + " set", new HashSet<>(expectedValid), - realValid.stream().map(c -> c.getDescription()).collect(Collectors.toSet())); - assertEquals("Wrong invalid constructions in " + type + " set", new HashSet<>(expectedInvalid), - realInvalid.stream().map(c -> c.getDescription()).collect(Collectors.toSet())); + assertUnsortedListsEquals("Wrong valid constructions in " + type + " set", expectedValid, + realValid, c -> c.getDescription()); + assertUnsortedListsEquals("Wrong invalid constructions in " + type + " set", expectedInvalid, + realInvalid, c -> c.getDescription()); } - private Collection assertAssignmentTripleSetSize(LensContext context, int zero, int plus, int minus) { + @SuppressWarnings("unchecked") + private Collection> assertAssignmentTripleSetSize(LensContext context, int zero, int plus, int minus) { assertEquals("Wrong size of assignment triple zero set", zero, CollectionUtils.size(context.getEvaluatedAssignmentTriple().getZeroSet())); assertEquals("Wrong size of assignment triple plus set", plus, CollectionUtils.size(context.getEvaluatedAssignmentTriple().getPlusSet())); assertEquals("Wrong size of assignment triple minus set", minus, CollectionUtils.size(context.getEvaluatedAssignmentTriple().getMinusSet())); - return context.getEvaluatedAssignmentTriple().getAllValues(); + return (Collection) context.getEvaluatedAssignmentTriple().getAllValues(); } //endregion diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPasswordPolicy.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPasswordPolicy.java index 68a19df7706..7c83a3b2bf7 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPasswordPolicy.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPasswordPolicy.java @@ -35,7 +35,7 @@ import org.testng.annotations.Test; import com.evolveum.midpoint.model.common.stringpolicy.StringPolicyUtils; -import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyGenerator; +import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; import com.evolveum.midpoint.model.impl.AbstractInternalModelIntegrationTest; import com.evolveum.midpoint.model.impl.lens.projector.PasswordPolicyProcessor; import com.evolveum.midpoint.prism.PrismObject; @@ -74,7 +74,7 @@ public class TestPasswordPolicy extends AbstractInternalModelIntegrationTest { private PasswordPolicyProcessor passwordPolicyProcessor; @Autowired(required = true) - private ValuePolicyGenerator valuePolicyGenerator; + private ValuePolicyProcessor valuePolicyGenerator; @Test public void stringPolicyUtilsMinimalTest() throws JAXBException, SchemaException, IOException { diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractConfiguredModelIntegrationTest.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractConfiguredModelIntegrationTest.java index d956357bb4e..56254d3e8b5 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractConfiguredModelIntegrationTest.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractConfiguredModelIntegrationTest.java @@ -298,7 +298,7 @@ public class AbstractConfiguredModelIntegrationTest extends AbstractModelIntegra // Elaine has account on the dummy resources (default, red, blue) // The accounts are also assigned - static final String USER_ELAINE_FILENAME = COMMON_DIR + "/user-elaine.xml"; + static final File USER_ELAINE_FILE = new File (COMMON_DIR, "user-elaine.xml"); protected static final String USER_ELAINE_OID = "c0c010c0-d34d-b33f-f00d-11111111111e"; protected static final String USER_ELAINE_USERNAME = "elaine"; diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractInitializedModelIntegrationTest.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractInitializedModelIntegrationTest.java index cd104a88654..332b1515e10 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractInitializedModelIntegrationTest.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractInitializedModelIntegrationTest.java @@ -248,7 +248,7 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti userTypeJack = repoAddObjectFromFile(USER_JACK_FILE, UserType.class, true, initResult).asObjectable(); userTypeBarbossa = repoAddObjectFromFile(USER_BARBOSSA_FILE, UserType.class, initResult).asObjectable(); userTypeGuybrush = repoAddObjectFromFile(USER_GUYBRUSH_FILE, UserType.class, initResult).asObjectable(); - userTypeElaine = repoAddObjectFromFile(new File(USER_ELAINE_FILENAME), UserType.class, initResult).asObjectable(); + userTypeElaine = repoAddObjectFromFile(USER_ELAINE_FILE, UserType.class, initResult).asObjectable(); // Roles repoAddObjectFromFile(ROLE_PIRATE_FILE, initResult); diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestConnectorMultiInstance.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestConnectorMultiInstance.java index cae51f52dbc..2c74d4a7fc8 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestConnectorMultiInstance.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestConnectorMultiInstance.java @@ -84,6 +84,7 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti repoAddObjectFromFile(USER_JACK_FILE, true, initResult); repoAddObjectFromFile(USER_GUYBRUSH_FILE, true, initResult); + repoAddObjectFromFile(USER_ELAINE_FILE, true, initResult); } @Test diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMapping.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMapping.java index 8bca7312926..5263422d121 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMapping.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMapping.java @@ -1752,5 +1752,100 @@ public void test220NativeModifyDummyCrimsonThenChangePassword() throws Exception DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_DRINK_NAME, "brandy", "grappa"); } + + @Test + public void test299ModifyUserUnassignAccountDummyCrimson() throws Exception { + final String TEST_NAME = "test299ModifyUserUnassignAccountDummyCrimson"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(TestMapping.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + unassignAccount(USER_GUYBRUSH_OID, RESOURCE_DUMMY_CRIMSON_OID, null, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess("executeChanges result", result); + + PrismObject userAfter = getUser(USER_GUYBRUSH_OID); + display("User after", userAfter); + assertUser(userAfter, USER_GUYBRUSH_OID, USER_GUYBRUSH_USERNAME, USER_GUYBRUSH_FULL_NAME, + USER_GUYBRUSH_GIVEN_NAME, USER_GUYBRUSH_FAMILY_NAME); + assertNoAssignments(userAfter); + assertLinks(userAfter, 0); + + // Check account in dummy resource + assertNoDummyAccount(RESOURCE_DUMMY_CRIMSON_NAME, ACCOUNT_GUYBRUSH_DUMMY_USERNAME); + + } + + /** + * MID-3816 + */ + @Test + public void test300AssignGuybrushDummyYellow() throws Exception { + final String TEST_NAME = "test300AssignGuybrushDummyYellow"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(TestMapping.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + assignAccount(USER_GUYBRUSH_OID, RESOURCE_DUMMY_YELLOW_OID, null, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userAfter = getUser(USER_GUYBRUSH_OID); + display("User after", userAfter); + assertUser(userAfter, USER_GUYBRUSH_OID, USER_GUYBRUSH_USERNAME, USER_GUYBRUSH_FULL_NAME, + USER_GUYBRUSH_GIVEN_NAME, USER_GUYBRUSH_FAMILY_NAME); + + // Check account in dummy resource + DummyAccount dummyAccount = assertDummyAccount(RESOURCE_DUMMY_YELLOW_NAME, ACCOUNT_GUYBRUSH_DUMMY_USERNAME, + ACCOUNT_GUYBRUSH_DUMMY_FULLNAME, true); + display("Dummy account", dummyAccount); + assertDummyAccountAttribute(RESOURCE_DUMMY_YELLOW_NAME, ACCOUNT_GUYBRUSH_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_QUOTE_NAME, "Bla bla bla administrator -- administrator"); + assertDummyAccountAttribute(RESOURCE_DUMMY_YELLOW_NAME, ACCOUNT_GUYBRUSH_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_GOSSIP_NAME, "Some say elaine -- administrator"); + } + + @Test + public void test309UnassignGuybrushDummyYellow() throws Exception { + final String TEST_NAME = "test309UnassignGuybrushDummyYellow"; + TestUtil.displayTestTile(this, TEST_NAME); + // GIVEN + Task task = taskManager.createTaskInstance(TestMapping.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + unassignAccount(USER_GUYBRUSH_OID, RESOURCE_DUMMY_YELLOW_OID, null, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess("executeChanges result", result); + + PrismObject userAfter = getUser(USER_GUYBRUSH_OID); + display("User after", userAfter); + assertUser(userAfter, USER_GUYBRUSH_OID, USER_GUYBRUSH_USERNAME, USER_GUYBRUSH_FULL_NAME, + USER_GUYBRUSH_GIVEN_NAME, USER_GUYBRUSH_FAMILY_NAME); + assertNoAssignments(userAfter); + assertLinks(userAfter, 0); + + // Check account in dummy resource + assertNoDummyAccount(RESOURCE_DUMMY_YELLOW_NAME, ACCOUNT_GUYBRUSH_DUMMY_USERNAME); + + } } diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java index 5df5f75c995..413beef848c 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java @@ -1132,7 +1132,7 @@ public void test131ModifyUserJackAssignAccount() throws Exception { TestUtil.assertSuccess("executeChanges result", result); XMLGregorianCalendar endTime = clock.currentTimeXMLGregorianCalendar(); assertShadowFetchOperationCountIncrement(0); - assertPrismObjectCloneIncrement(64); + assertPrismObjectCloneIncrement(65); PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/AbstractPasswordTest.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/AbstractPasswordTest.java index 1e5994d4b67..02281f77837 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/AbstractPasswordTest.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/AbstractPasswordTest.java @@ -39,10 +39,13 @@ import com.evolveum.icf.dummy.resource.ConflictException; import com.evolveum.icf.dummy.resource.SchemaViolationException; import com.evolveum.midpoint.model.intest.AbstractInitializedModelIntegrationTest; +import com.evolveum.midpoint.prism.Objectable; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismReferenceValue; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.util.PrismAsserts; +import com.evolveum.midpoint.prism.util.PrismTestUtil; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.internals.InternalsConfig; import com.evolveum.midpoint.schema.result.OperationResult; @@ -95,10 +98,10 @@ public abstract class AbstractPasswordTest extends AbstractInitializedModelInteg protected ResourceType resourceDummyUglyType; protected PrismObject resourceDummyUgly; - protected String accountOid; - protected String accountRedOid; - protected String accountUglyOid; - protected String accountYellowOid; + protected String accountJackOid; + protected String accountJackRedOid; + protected String accountJackUglyOid; + protected String accountJackYellowOid; protected XMLGregorianCalendar lastPasswordChangeStart; protected XMLGregorianCalendar lastPasswordChangeEnd; @@ -290,15 +293,17 @@ public void test100ModifyUserJackAssignAccount() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); assertUserJack(userJack); - accountOid = getSingleLinkOid(userJack); + accountJackOid = getSingleLinkOid(userJack); // Check shadow - PrismObject accountShadow = repositoryService.getObject(ShadowType.class, accountOid, null, result); - assertDummyAccountShadowRepo(accountShadow, accountOid, "jack"); + PrismObject accountShadow = repositoryService.getObject(ShadowType.class, accountJackOid, null, result); + assertDummyAccountShadowRepo(accountShadow, accountJackOid, "jack"); + assertShadowLifecycle(accountShadow, false); // Check account - PrismObject accountModel = modelService.getObject(ShadowType.class, accountOid, null, task, result); - assertDummyAccountShadowModel(accountModel, accountOid, "jack", "Jack Sparrow"); + PrismObject accountModel = modelService.getObject(ShadowType.class, accountJackOid, null, task, result); + assertDummyAccountShadowModel(accountModel, accountJackOid, "jack", "Jack Sparrow"); + assertShadowLifecycle(accountModel, false); // Check account in dummy resource assertDefaultDummyAccount(ACCOUNT_JACK_DUMMY_USERNAME, "Jack Sparrow", true); @@ -353,7 +358,7 @@ public void test111ModifyAccountJackPassword() throws Exception { assumeAssignmentPolicy(AssignmentPolicyEnforcementType.FULL); // WHEN - modifyAccountChangePassword(accountOid, USER_PASSWORD_3_CLEAR, task, result); + modifyAccountChangePassword(accountJackOid, USER_PASSWORD_3_CLEAR, task, result); // THEN result.computeStatus(); @@ -391,7 +396,7 @@ public void test112ModifyJackPasswordUserAndAccount() throws Exception { ProtectedStringType userPasswordPs5 = new ProtectedStringType(); userPasswordPs5.setClearValue(USER_PASSWORD_5_CLEAR); - ObjectDelta accountDelta = createModifyAccountShadowReplaceDelta(accountOid, getDummyResourceObject(), + ObjectDelta accountDelta = createModifyAccountShadowReplaceDelta(accountJackOid, getDummyResourceObject(), PASSWORD_VALUE_PATH, userPasswordPs5); Collection> deltas = MiscSchemaUtil.createCollection(accountDelta, userDelta); @@ -444,8 +449,8 @@ public void test120ModifyUserJackAssignAccountDummyRedAndUgly() throws Exception display("User after change execution", userJack); assertUserJack(userJack); assertLinks(userJack, 3); - accountRedOid = getLinkRefOid(userJack, RESOURCE_DUMMY_RED_OID); - accountUglyOid = getLinkRefOid(userJack, RESOURCE_DUMMY_UGLY_OID); + accountJackRedOid = getLinkRefOid(userJack, RESOURCE_DUMMY_RED_OID); + accountJackUglyOid = getLinkRefOid(userJack, RESOURCE_DUMMY_UGLY_OID); // Check account in dummy resource assertDummyAccount(RESOURCE_DUMMY_RED_NAME, ACCOUNT_JACK_DUMMY_USERNAME, "Jack Sparrow", true); @@ -481,7 +486,7 @@ public void test121ModifyJackPasswordUserAndAccountRed() throws Exception { ProtectedStringType userPasswordPs2 = new ProtectedStringType(); userPasswordPs2.setClearValue(USER_PASSWORD_2_CLEAR); - ObjectDelta accountDelta = createModifyAccountShadowReplaceDelta(accountRedOid, getDummyResourceObject(), + ObjectDelta accountDelta = createModifyAccountShadowReplaceDelta(accountJackRedOid, getDummyResourceObject(), PASSWORD_VALUE_PATH, userPasswordPs2); Collection> deltas = MiscSchemaUtil.createCollection(userDelta, accountDelta); @@ -617,7 +622,7 @@ public void test130ModifyUserJackAssignAccountDummyYellow() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); assertLinks(userJack, 4); - accountYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); + accountJackYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); // Check account in dummy resource (yellow) assertDummyAccount(RESOURCE_DUMMY_YELLOW_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); @@ -667,7 +672,7 @@ public void test132ModifyUserJackPasswordA() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); assertLinks(userJack, 4); - accountYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); + accountJackYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); // Check account in dummy resource (yellow): password is too short for this, original password should remain there assertDummyAccount(RESOURCE_DUMMY_YELLOW_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); @@ -736,7 +741,7 @@ public void test202ReconcileUserJack() throws Exception { PrismObject userAfter = getUser(USER_JACK_OID); display("User after", userAfter); assertLinks(userAfter, 4); - accountYellowOid = getLinkRefOid(userAfter, RESOURCE_DUMMY_YELLOW_OID); + accountJackYellowOid = getLinkRefOid(userAfter, RESOURCE_DUMMY_YELLOW_OID); // Check account in dummy resource (yellow): password is too short for this, original password should remain there assertDummyAccount(RESOURCE_DUMMY_YELLOW_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); @@ -1006,7 +1011,7 @@ private void assertJackPasswordsWithHistory(String expectedCurrentPassword, Stri PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); assertLinks(userJack, 4); - accountYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); + accountJackYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); assertUserPassword(userJack, expectedCurrentPassword); assertPasswordMetadata(userJack, false, lastPasswordChangeStart, lastPasswordChangeEnd); @@ -1074,7 +1079,7 @@ public void test300TwoParentOrgRefs() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); assertLinks(userJack, 4); - accountYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); + accountJackYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); // Make sure that the password is unchanged @@ -1174,9 +1179,11 @@ public void test330RemoveEmployeeNumber() throws Exception { assumeAssignmentPolicy(AssignmentPolicyEnforcementType.FULL); // WHEN + TestUtil.displayWhen(TEST_NAME); modifyUserReplace(USER_JACK_OID, UserType.F_EMPLOYEE_NUMBER, task, result); // THEN + TestUtil.displayThen(TEST_NAME); result.computeStatus(); TestUtil.assertSuccess(result); @@ -1191,6 +1198,336 @@ public void test330RemoveEmployeeNumber() throws Exception { assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, null); } + + /** + * Add user with password and an assignment. check that the account is provisioned and has password. + * Tests proper initial cleartext password handling in all cases. + */ + @Test + public void test400AddUserRappWithAssignment() throws Exception { + final String TEST_NAME = "test400AddUserRappWithAssignment"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(AbstractPasswordTest.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = PrismTestUtil.parseObject(USER_RAPP_FILE); + AssignmentType assignmentType = createConstructionAssignment(RESOURCE_DUMMY_OID, null, null); + UserType userBeforeType = userBefore.asObjectable(); + userBeforeType.setFullName(createPolyStringType(USER_RAPP_FULLNAME)); + userBeforeType.getAssignment().add(assignmentType); + setPassword(userBefore, USER_PASSWORD_VALID_1); + display("User before", userBefore); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + addObject(userBefore, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userAfter = getUser(USER_RAPP_OID); + display("User after", userAfter); + String accountOid = getSingleLinkOid(userAfter); + + PrismObject accountShadow = repositoryService.getObject(ShadowType.class, accountOid, null, result); + assertDummyAccountShadowRepo(accountShadow, accountOid, USER_RAPP_USERNAME); + assertShadowLifecycle(accountShadow, true); + + // Check account + PrismObject accountModel = modelService.getObject(ShadowType.class, accountOid, null, task, result); + assertDummyAccountShadowModel(accountModel, accountOid, USER_RAPP_USERNAME, USER_RAPP_FULLNAME); + assertShadowLifecycle(accountModel, true); + + // Check account in dummy resource + assertDefaultDummyAccount(USER_RAPP_USERNAME, USER_RAPP_FULLNAME, true); + assertDummyPassword(null, USER_RAPP_USERNAME, USER_PASSWORD_VALID_1); + } + + /** + * Make sure recompute does not destroy the situation. + */ + @Test + public void test401UserRappRecompute() throws Exception { + final String TEST_NAME = "test401UserRappRecompute"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(AbstractPasswordTest.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + recomputeUser(USER_RAPP_OID, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userAfter = getUser(USER_RAPP_OID); + display("User after", userAfter); + String accountOid = getSingleLinkOid(userAfter); + + PrismObject accountShadow = repositoryService.getObject(ShadowType.class, accountOid, null, result); + assertDummyAccountShadowRepo(accountShadow, accountOid, USER_RAPP_USERNAME); + assertShadowLifecycle(accountShadow, true); + + // Check account + PrismObject accountModel = modelService.getObject(ShadowType.class, accountOid, null, task, result); + assertDummyAccountShadowModel(accountModel, accountOid, USER_RAPP_USERNAME, USER_RAPP_FULLNAME); + assertShadowLifecycle(accountModel, true); + + // Check account in dummy resource + assertDefaultDummyAccount(USER_RAPP_USERNAME, USER_RAPP_FULLNAME, true); + assertDummyPassword(null, USER_RAPP_USERNAME, USER_PASSWORD_VALID_1); + } + + /** + * add new assignment to the user, check that account is provisioned and has correct lifecycle + */ + @Test + public void test402AssignRappDummyRed() throws Exception { + final String TEST_NAME = "test402AssignRappDummyRed"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(AbstractPasswordTest.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_RAPP_OID); + display("User before", userBefore); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + assignAccount(USER_RAPP_OID, RESOURCE_DUMMY_RED_OID, null, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userAfter = getUser(USER_RAPP_OID); + display("User after", userAfter); + + String accountDefaultOid = getLinkRefOid(userAfter, RESOURCE_DUMMY_OID); + String accountRedOid = getLinkRefOid(userAfter, RESOURCE_DUMMY_RED_OID); + + // Check account in dummy RED resource + assertDummyAccount(RESOURCE_DUMMY_RED_NAME, USER_RAPP_USERNAME, USER_RAPP_FULLNAME, true); + assertDummyPasswordConditional(RESOURCE_DUMMY_RED_NAME, USER_RAPP_USERNAME, USER_PASSWORD_VALID_1); + + // RED shadows + PrismObject accountShadowRed = repositoryService.getObject(ShadowType.class, accountRedOid, null, result); + display("Repo shadow RED", accountShadowRed); + assertAccountShadowRepo(accountShadowRed, accountRedOid, USER_RAPP_USERNAME, getDummyResourceType(RESOURCE_DUMMY_RED_NAME)); + assertShadowLifecycle(accountShadowRed, false); + + PrismObject accountModelRed = modelService.getObject(ShadowType.class, accountRedOid, null, task, result); + display("Model shadow RED", accountModelRed); + assertAccountShadowModel(accountModelRed, accountRedOid, USER_RAPP_USERNAME, getDummyResourceType(RESOURCE_DUMMY_RED_NAME)); + assertShadowLifecycle(accountModelRed, false); + + // DEFAULT shadows + PrismObject accountShadow = repositoryService.getObject(ShadowType.class, accountDefaultOid, null, result); + assertDummyAccountShadowRepo(accountShadow, accountDefaultOid, USER_RAPP_USERNAME); + assertShadowLifecycle(accountShadow, null); + + PrismObject accountModel = modelService.getObject(ShadowType.class, accountDefaultOid, null, task, result); + assertDummyAccountShadowModel(accountModel, accountDefaultOid, USER_RAPP_USERNAME, USER_RAPP_FULLNAME); + assertShadowLifecycle(accountModel, null); + + assertUserPassword(userAfter, USER_PASSWORD_VALID_1); + assertDefaultDummyAccount(USER_RAPP_USERNAME, USER_RAPP_FULLNAME, true); + assertDummyPassword(null, USER_RAPP_USERNAME, USER_PASSWORD_VALID_1); + } + + /** + * Make sure recompute does not destroy the situation. + */ + @Test + public void test403UserRappRecompute() throws Exception { + final String TEST_NAME = "test403UserRappRecompute"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(AbstractPasswordTest.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_RAPP_OID); + display("User before", userBefore); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + recomputeUser(USER_RAPP_OID, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userAfter = getUser(USER_RAPP_OID); + display("User after", userAfter); + + String accountDefaultOid = getLinkRefOid(userAfter, RESOURCE_DUMMY_OID); + String accountRedOid = getLinkRefOid(userAfter, RESOURCE_DUMMY_RED_OID); + + // Check account in dummy RED resource + assertDummyAccount(RESOURCE_DUMMY_RED_NAME, USER_RAPP_USERNAME, USER_RAPP_FULLNAME, true); + assertDummyPasswordConditional(RESOURCE_DUMMY_RED_NAME, USER_RAPP_USERNAME, USER_PASSWORD_VALID_1); + + // RED shadows + PrismObject accountShadowRed = repositoryService.getObject(ShadowType.class, accountRedOid, null, result); + display("Repo shadow RED", accountShadowRed); + assertAccountShadowRepo(accountShadowRed, accountRedOid, USER_RAPP_USERNAME, getDummyResourceType(RESOURCE_DUMMY_RED_NAME)); + assertShadowLifecycle(accountShadowRed, false); + + PrismObject accountModelRed = modelService.getObject(ShadowType.class, accountRedOid, null, task, result); + display("Model shadow RED", accountModelRed); + assertAccountShadowModel(accountModelRed, accountRedOid, USER_RAPP_USERNAME, getDummyResourceType(RESOURCE_DUMMY_RED_NAME)); + assertShadowLifecycle(accountModelRed, false); + + // DEFAULT shadows + PrismObject accountShadow = repositoryService.getObject(ShadowType.class, accountDefaultOid, null, result); + assertDummyAccountShadowRepo(accountShadow, accountDefaultOid, USER_RAPP_USERNAME); + assertShadowLifecycle(accountShadow, null); + + PrismObject accountModel = modelService.getObject(ShadowType.class, accountDefaultOid, null, task, result); + assertDummyAccountShadowModel(accountModel, accountDefaultOid, USER_RAPP_USERNAME, USER_RAPP_FULLNAME); + assertShadowLifecycle(accountModel, null); + + assertUserPassword(userAfter, USER_PASSWORD_VALID_1); + assertDefaultDummyAccount(USER_RAPP_USERNAME, USER_RAPP_FULLNAME, true); + assertDummyPassword(null, USER_RAPP_USERNAME, USER_PASSWORD_VALID_1); + } + + /** + * initialize the account (password and lifecycle delta), check accoutn password and lifecycle + */ + @Test + public void test404InitializeRappDummyRed() throws Exception { + final String TEST_NAME = "test404InitializeRappDummyRed"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(AbstractPasswordTest.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_RAPP_OID); + display("User before", userBefore); + String accountRedOid = getLinkRefOid(userBefore, RESOURCE_DUMMY_RED_OID); + + ObjectDelta shadowDelta = ObjectDelta.createEmptyModifyDelta(ShadowType.class, accountRedOid, prismContext); + ProtectedStringType passwordPs = new ProtectedStringType(); + passwordPs.setClearValue(USER_PASSWORD_VALID_1); + shadowDelta.addModificationReplaceProperty(SchemaConstants.PATH_PASSWORD_VALUE, passwordPs); + shadowDelta.addModificationReplaceProperty(ObjectType.F_LIFECYCLE_STATE, SchemaConstants.LIFECYCLE_ACTIVE); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + executeChanges(shadowDelta, null, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userAfter = getUser(USER_RAPP_OID); + display("User after", userAfter); + + String accountDefaultOid = getLinkRefOid(userAfter, RESOURCE_DUMMY_OID); + accountRedOid = getLinkRefOid(userAfter, RESOURCE_DUMMY_RED_OID); + + // Check account in dummy RED resource + assertDummyAccount(RESOURCE_DUMMY_RED_NAME, USER_RAPP_USERNAME, USER_RAPP_FULLNAME, true); + assertDummyPassword(RESOURCE_DUMMY_RED_NAME, USER_RAPP_USERNAME, USER_PASSWORD_VALID_1); + + // RED shadows + PrismObject accountShadowRed = repositoryService.getObject(ShadowType.class, accountRedOid, null, result); + display("Repo shadow RED", accountShadowRed); + assertAccountShadowRepo(accountShadowRed, accountRedOid, USER_RAPP_USERNAME, getDummyResourceType(RESOURCE_DUMMY_RED_NAME)); + assertShadowLifecycle(accountShadowRed, SchemaConstants.LIFECYCLE_ACTIVE); + + PrismObject accountModelRed = modelService.getObject(ShadowType.class, accountRedOid, null, task, result); + display("Model shadow RED", accountModelRed); + assertAccountShadowModel(accountModelRed, accountRedOid, USER_RAPP_USERNAME, getDummyResourceType(RESOURCE_DUMMY_RED_NAME)); + assertShadowLifecycle(accountModelRed, SchemaConstants.LIFECYCLE_ACTIVE); + + // DEFAULT shadows + PrismObject accountShadow = repositoryService.getObject(ShadowType.class, accountDefaultOid, null, result); + assertDummyAccountShadowRepo(accountShadow, accountDefaultOid, USER_RAPP_USERNAME); + assertShadowLifecycle(accountShadow, null); + + PrismObject accountModel = modelService.getObject(ShadowType.class, accountDefaultOid, null, task, result); + assertDummyAccountShadowModel(accountModel, accountDefaultOid, USER_RAPP_USERNAME, USER_RAPP_FULLNAME); + assertShadowLifecycle(accountModel, null); + + assertUserPassword(userAfter, USER_PASSWORD_VALID_1); + assertDefaultDummyAccount(USER_RAPP_USERNAME, USER_RAPP_FULLNAME, true); + assertDummyPassword(null, USER_RAPP_USERNAME, USER_PASSWORD_VALID_1); + } + + /** + * Make sure recompute does not destroy the situation. + */ + @Test + public void test405UserRappRecompute() throws Exception { + final String TEST_NAME = "test405UserRappRecompute"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(AbstractPasswordTest.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_RAPP_OID); + display("User before", userBefore); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + recomputeUser(USER_RAPP_OID, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userAfter = getUser(USER_RAPP_OID); + display("User after", userAfter); + + String accountDefaultOid = getLinkRefOid(userAfter, RESOURCE_DUMMY_OID); + String accountRedOid = getLinkRefOid(userAfter, RESOURCE_DUMMY_RED_OID); + + // Check account in dummy RED resource + assertDummyAccount(RESOURCE_DUMMY_RED_NAME, USER_RAPP_USERNAME, USER_RAPP_FULLNAME, true); + assertDummyPassword(RESOURCE_DUMMY_RED_NAME, USER_RAPP_USERNAME, USER_PASSWORD_VALID_1); + + // RED shadows + PrismObject accountShadowRed = repositoryService.getObject(ShadowType.class, accountRedOid, null, result); + display("Repo shadow RED", accountShadowRed); + assertAccountShadowRepo(accountShadowRed, accountRedOid, USER_RAPP_USERNAME, getDummyResourceType(RESOURCE_DUMMY_RED_NAME)); + assertShadowLifecycle(accountShadowRed, SchemaConstants.LIFECYCLE_ACTIVE); + + PrismObject accountModelRed = modelService.getObject(ShadowType.class, accountRedOid, null, task, result); + display("Model shadow RED", accountModelRed); + assertAccountShadowModel(accountModelRed, accountRedOid, USER_RAPP_USERNAME, getDummyResourceType(RESOURCE_DUMMY_RED_NAME)); + assertShadowLifecycle(accountModelRed, SchemaConstants.LIFECYCLE_ACTIVE); + + // DEFAULT shadows + PrismObject accountShadow = repositoryService.getObject(ShadowType.class, accountDefaultOid, null, result); + assertDummyAccountShadowRepo(accountShadow, accountDefaultOid, USER_RAPP_USERNAME); + assertShadowLifecycle(accountShadow, null); + + PrismObject accountModel = modelService.getObject(ShadowType.class, accountDefaultOid, null, task, result); + assertDummyAccountShadowModel(accountModel, accountDefaultOid, USER_RAPP_USERNAME, USER_RAPP_FULLNAME); + assertShadowLifecycle(accountModel, null); + + assertUserPassword(userAfter, USER_PASSWORD_VALID_1); + assertDefaultDummyAccount(USER_RAPP_USERNAME, USER_RAPP_FULLNAME, true); + assertDummyPassword(null, USER_RAPP_USERNAME, USER_PASSWORD_VALID_1); + } private void assertDummyPassword(String userId, String expectedClearPassword) throws SchemaViolationException, ConflictException { @@ -1209,4 +1546,16 @@ protected void assertDummyPasswordConditional(String instance, String userId, St } } + protected abstract void assertShadowLifecycle(PrismObject shadow, boolean focusCreated); + + protected void assertShadowLifecycle(PrismObject shadow, String expectedLifecycle) { + if (expectedLifecycle == null) { + String actualLifecycle = shadow.asObjectable().getLifecycleState(); + if (actualLifecycle != null && !SchemaConstants.LIFECYCLE_ACTIVE.equals(actualLifecycle)) { + fail("Expected default lifecycle for "+shadow+", but was "+actualLifecycle); + } + } else { + PrismAsserts.assertPropertyValue(shadow, ObjectType.F_LIFECYCLE_STATE, expectedLifecycle); + } + } } diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefault.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefault.java index 53b183659aa..4fa81b5062c 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefault.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefault.java @@ -22,8 +22,10 @@ import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; +import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; /** * Password test with DEFAULT configuration of password storage. @@ -45,5 +47,10 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti protected String getSecurityPolicyOid() { return SECURITY_POLICY_OID; } + + @Override + protected void assertShadowLifecycle(PrismObject shadow, boolean focusCreated) { + assertShadowLifecycle(shadow, null); + } } diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefaultHashing.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefaultHashing.java index e1d7993f8b8..6b3bd88eea0 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefaultHashing.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefaultHashing.java @@ -22,9 +22,12 @@ import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsStorageTypeType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; /** * Password test with HASHING storage for all credential types. @@ -49,6 +52,15 @@ protected String getSecurityPolicyOid() { @Override protected CredentialsStorageTypeType getPasswordStorageType() { return CredentialsStorageTypeType.HASHING; - } + } + + @Override + protected void assertShadowLifecycle(PrismObject shadow, boolean focusCreated) { + if (focusCreated) { + assertShadowLifecycle(shadow, null); + } else { + assertShadowLifecycle(shadow, SchemaConstants.LIFECYCLE_PROPOSED); + } + } } diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordNone.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordNone.java index 0173cf133ba..1a7cec36e4e 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordNone.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordNone.java @@ -22,9 +22,12 @@ import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsStorageTypeType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; /** * Password test with NONE password storage (default storage for other types) @@ -56,4 +59,13 @@ protected String getSecurityPolicyOid() { protected CredentialsStorageTypeType getPasswordStorageType() { return CredentialsStorageTypeType.NONE; } + + @Override + protected void assertShadowLifecycle(PrismObject shadow, boolean focusCreated) { + if (focusCreated) { + assertShadowLifecycle(shadow, null); + } else { + assertShadowLifecycle(shadow, SchemaConstants.LIFECYCLE_PROPOSED); + } + } } diff --git a/model/model-intest/src/test/resources/common/password-policy-global.xml b/model/model-intest/src/test/resources/common/password-policy-global.xml index b9c2c61cc7a..6b5b6a2f3fb 100644 --- a/model/model-intest/src/test/resources/common/password-policy-global.xml +++ b/model/model-intest/src/test/resources/common/password-policy-global.xml @@ -39,7 +39,7 @@ assert input != null assert object != null - assert object.getName() != null + // object.getName() may be null (e.g. new shadow) if (object instanceof com.evolveum.midpoint.xml.ns._public.common.common_3.UserType) { return !basic.containsIgnoreCase(input, object.getName()) } else { @@ -56,7 +56,7 @@ assert input != null assert object != null - assert object.getName() != null + // object.getName() may be null (e.g. new shadow) if (object instanceof com.evolveum.midpoint.xml.ns._public.common.common_3.UserType) { return !basic.containsIgnoreCase(input, object.getFamilyName()) && !basic.containsIgnoreCase(input, object.getGivenName()) && !basic.containsIgnoreCase(input, object.getAdditionalName()) } else { diff --git a/model/model-intest/src/test/resources/common/resource-dummy-yellow.xml b/model/model-intest/src/test/resources/common/resource-dummy-yellow.xml index cfe984a89dc..739a368c257 100644 --- a/model/model-intest/src/test/resources/common/resource-dummy-yellow.xml +++ b/model/model-intest/src/test/resources/common/resource-dummy-yellow.xml @@ -198,14 +198,28 @@ strong - Arr! + ri:gossip Gossip - true + + + + + + + ri:water diff --git a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlAuditServiceImpl.java b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlAuditServiceImpl.java index 9b950d01925..26f0e78843a 100644 --- a/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlAuditServiceImpl.java +++ b/repo/repo-sql-impl/src/main/java/com/evolveum/midpoint/repo/sql/SqlAuditServiceImpl.java @@ -370,7 +370,7 @@ private void cleanupAuditMaxAge(CleanupPolicyType policy, OperationResult parent while (true) { try { LOGGER.info("{} audit cleanup, deleting up to {} (duration '{}'), batch size {}{}.", - first ? "Starting" : "Restarting", minValue, duration, CLEANUP_AUDIT_BATCH_SIZE, + first ? "Starting" : "Continuing with ", minValue, duration, CLEANUP_AUDIT_BATCH_SIZE, first ? "" : ", up to now deleted " + totalCountHolder.getValue() + " entries"); first = false; int count; @@ -422,7 +422,7 @@ private void cleanupAuditMaxRecords(CleanupPolicyType policy, OperationResult pa while (true) { try { LOGGER.info("{} audit cleanup, keeping at most {} records, batch size {}{}.", - first ? "Starting" : "Restarting", recordsToKeep, CLEANUP_AUDIT_BATCH_SIZE, + first ? "Starting" : "Continuing with ", recordsToKeep, CLEANUP_AUDIT_BATCH_SIZE, first ? "" : ", up to now deleted " + totalCountHolder.getValue() + " entries"); first = false; int count; diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java index 417362f6201..5c12997a410 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java @@ -1464,4 +1464,21 @@ protected void logTrustManagers() throws NoSuchAlgorithmException, KeyStoreExcep } } } + + protected void setPassword(PrismObject user, String password) { + UserType userType = user.asObjectable(); + CredentialsType creds = userType.getCredentials(); + if (creds == null) { + creds = new CredentialsType(); + userType.setCredentials(creds); + } + PasswordType passwordType = creds.getPassword(); + if (passwordType == null) { + passwordType = new PasswordType(); + creds.setPassword(passwordType); + } + ProtectedStringType ps = new ProtectedStringType(); + ps.setClearValue(password); + passwordType.setValue(ps); + } } diff --git a/repo/security-api/src/main/java/com/evolveum/midpoint/security/api/Authorization.java b/repo/security-api/src/main/java/com/evolveum/midpoint/security/api/Authorization.java index 6744ae00a51..81c19d7b019 100644 --- a/repo/security-api/src/main/java/com/evolveum/midpoint/security/api/Authorization.java +++ b/repo/security-api/src/main/java/com/evolveum/midpoint/security/api/Authorization.java @@ -16,6 +16,7 @@ package com.evolveum.midpoint.security.api; import java.util.List; +import java.util.Objects; import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; @@ -132,7 +133,22 @@ public String debugDump(int indent) { @Override public String toString() { - return "Authorization(" + authorizationType == null ? "null" : authorizationType.getAction() + ")"; + return "Authorization(" + (authorizationType == null ? "null" : authorizationType.getAction() + ")"); } + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof Authorization)) + return false; + Authorization that = (Authorization) o; + return Objects.equals(authorizationType, that.authorizationType) && + Objects.equals(sourceDescription, that.sourceDescription); + } + + @Override + public int hashCode() { + return Objects.hash(authorizationType, sourceDescription); + } } diff --git a/samples/rest/policy-items-definition-generate-role.xml b/samples/rest/policy-items-definition-generate-role.xml new file mode 100644 index 00000000000..9446647b46b --- /dev/null +++ b/samples/rest/policy-items-definition-generate-role.xml @@ -0,0 +1,13 @@ + + + + identifier + + + + \ No newline at end of file diff --git a/samples/rest/policy-items-definition-generate.xml b/samples/rest/policy-items-definition-generate.xml new file mode 100644 index 00000000000..a86ab411e43 --- /dev/null +++ b/samples/rest/policy-items-definition-generate.xml @@ -0,0 +1,26 @@ + + + + employeeType + + + + + + employeeNumber + + true + + + + + phoneNumber + + + + \ No newline at end of file diff --git a/samples/rest/policy-items-definition-validate.xml b/samples/rest/policy-items-definition-validate.xml new file mode 100644 index 00000000000..b301ea77948 --- /dev/null +++ b/samples/rest/policy-items-definition-validate.xml @@ -0,0 +1,14 @@ + + + + employeeNumber + + true + + + \ No newline at end of file diff --git a/samples/tasks/bulk-actions/assign-resource-to-selected-users.xml b/samples/tasks/bulk-actions/assign-resource-to-selected-users.xml index 8f952ddee84..e290c191f6d 100644 --- a/samples/tasks/bulk-actions/assign-resource-to-selected-users.xml +++ b/samples/tasks/bulk-actions/assign-resource-to-selected-users.xml @@ -17,7 +17,9 @@ + xmlns:q="http://prism.evolveum.com/xml/ns/public/query-3" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema"> Assign OpenDJ account to users starting with 'a' @@ -37,7 +39,7 @@ assign resource - ef2bc95b-76e0-48e2-86d6-3d4f02d3e1a2 + ef2bc95b-76e0-48e2-86d6-3d4f02d3e1a2