Repository: SFDO-Tooling/CumulusCI
Pull request #3040 (2022-01-19 20:39:36)

Adjust marketing_cloud_create_user task
# Critical Changes

# Changes
* The `marketing_cloud_create_user` task now creates an unlocked user with a notification email address so that it is possible for the user to log in. This task also has a new option, `activate_if_existing`, which can be set to True to ensure that if the user already exists in an inactive state, it will be activated.

# Issues Closed



Patch:
diff --git a/cumulusci/salesforce_api/mc_soap_envelopes.py b/cumulusci/salesforce_api/mc_soap_envelopes.py
index 584e380bd..bb99cbe26 100644
--- a/cumulusci/salesforce_api/mc_soap_envelopes.py
+++ b/cumulusci/salesforce_api/mc_soap_envelopes.py
@@ -44,11 +44,14 @@ CREATE_USER = """<?xml version="1.0" encoding="utf-8"?>
                 {user_name}
                 <!-- REQUIRED: Set the user email -->
                 <Email>{user_email}</Email>
+                <NotificationEmailAddress>{user_email}</NotificationEmailAddress>
                 <!-- REQUIRED: Set the user password -->
                 <Password>{user_password}</Password>
                 <!-- REQUIRED: Set the username -->
                 <UserID>{user_username}</UserID>
                 <IsAPIUser>true</IsAPIUser>
+                {active_flag}
+                <IsLocked>false</IsLocked>
                 <!-- OPTIONAL: Include this if you want to assign roles to new user -->
                 <!-- IDs for system defined roles located here: https://developer.salesforce.com/docs/atlas.en-us.noversion.mc-apis.meta/mc-apis/setting_user_permissions_via_the_web_services_api.htm -->
                 {role_id}
diff --git a/cumulusci/tasks/marketing_cloud/api.py b/cumulusci/tasks/marketing_cloud/api.py
index 777a718a2..9ae269ac5 100644
--- a/cumulusci/tasks/marketing_cloud/api.py
+++ b/cumulusci/tasks/marketing_cloud/api.py
@@ -1,7 +1,7 @@
 import requests
 
+from cumulusci.core.utils import process_bool_arg
 from cumulusci.salesforce_api import mc_soap_envelopes as envelopes
-from cumulusci.utils.xml import lxml_parse_string
 
 from .base import BaseMarketingCloudTask
 
@@ -24,31 +24,16 @@ class CreateSubscriberAttribute(BaseMarketingCloudTask):
             access_token=self.mc_config.access_token,
             attribute_name=attribute_name,
         )
-        # construct request
         response = requests.post(
             f"{self.mc_config.soap_instance_url}Service.asmx",
             data=envelope.encode("utf-8"),
             headers={"Content-Type": "text/xml; charset=utf-8"},
         )
-        response.raise_for_status()
-        # check resulting status code
-        root = lxml_parse_string(response.content)
-        status_code = root.find(
-            ".//{http://exacttarget.com/wsdl/partnerAPI}StatusCode"
-        ).text
-        status_message = root.find(
-            ".//{http://exacttarget.com/wsdl/partnerAPI}StatusMessage"
-        ).text
-        success = True
-        if status_code == "OK":
-            self.logger.info(
-                f"Successfully created subscriber attribute: {attribute_name}."
-            )
-        if status_code != "OK":
-            raise Exception(
-                f"Error from Marketing Cloud: {status_message} \n\nFull response text: {response.text}"
-            )
-        self.return_values = {"success": success}
+        self._check_soap_response(response)
+        self.logger.info(
+            f"Successfully created subscriber attribute: {attribute_name}."
+        )
+        self.return_values = {"success": True}
 
 
 class CreateUser(BaseMarketingCloudTask):
@@ -86,6 +71,10 @@ class CreateUser(BaseMarketingCloudTask):
             "description": "Assign a Role to the new User, specified as an ID. IDs for system defined roles located here: https://developer.salesforce.com/docs/atlas.en-us.noversion.mc-apis.meta/mc-apis/setting_user_permissions_via_the_web_services_api.htm",
             "required": False,
         },
+        "activate_if_existing": {
+            "description": "Activate the user if it already exists in an inactive state. Default: False",
+            "required": False,
+        },
     }
 
     def _run_task(self):
@@ -97,6 +86,8 @@ class CreateUser(BaseMarketingCloudTask):
         if external_key != "":
             external_key = f"<CustomerKey>{external_key}</CustomerKey>"
 
+        user_username = self.options.get("user_username")
+
         user_name = self.options.get("user_name", "")
         if user_name != "":
             user_name = f"<Name>{user_name}</Name>"
@@ -105,6 +96,12 @@ class CreateUser(BaseMarketingCloudTask):
         if role_id != "":
             role_id = f"<UserPermissions><ID>{role_id}</ID></UserPermissions>"
 
+        active_flag = (
+            "<ActiveFlag>true</ActiveFlag>"
+            if process_bool_arg(self.options.get("activate_if_existing"))
+            else ""
+        )
+
         envelope = envelope.format(
             soap_instance_url=self.mc_config.soap_instance_url,
             access_token=self.mc_config.access_token,
@@ -114,34 +111,18 @@ class CreateUser(BaseMarketingCloudTask):
             user_name=user_name,
             user_email=self.options.get("user_email"),
             user_password=self.options.get("user_password"),
-            user_username=self.options.get("user_username"),
+            user_username=user_username,
             role_id=role_id,
+            active_flag=active_flag,
         )
-        # construct request
-        # TO DO: DRY refactor
         response = requests.post(
             f"{self.mc_config.soap_instance_url}Service.asmx",
             data=envelope.encode("utf-8"),
             headers={"Content-Type": "text/xml; charset=utf-8"},
         )
-        response.raise_for_status()
-        # check resulting status code
-        root = lxml_parse_string(response.content)
-        status_code = root.find(
-            ".//{http://exacttarget.com/wsdl/partnerAPI}StatusCode"
-        ).text
-        status_message = root.find(
-            ".//{http://exacttarget.com/wsdl/partnerAPI}StatusMessage"
-        ).text
-        success = True
-        if status_code == "OK":
-            user_username = self.options.get("user_username")
-            self.logger.info(f"Successfully created User: {user_username}.")
-        if status_code != "OK":
-            raise Exception(
-                f"Error from Marketing Cloud: {status_message}\n\nFull response text: {response.text}"
-            )
-        self.return_values = {"success": success}
+        self._check_soap_response(response)
+        self.logger.info(f"Successfully created User: {user_username}.")
+        self.return_values = {"success": True}
 
 
 class UpdateUserRole(BaseMarketingCloudTask):
@@ -196,28 +177,12 @@ class UpdateUserRole(BaseMarketingCloudTask):
             user_password=self.options.get("user_password"),
             role_id=self.options.get("role_id"),
         )
-        # construct request
-        # TO DO: DRY refactor
         response = requests.post(
             f"{self.mc_config.soap_instance_url}Service.asmx",
             data=envelope.encode("utf-8"),
             headers={"Content-Type": "text/xml; charset=utf-8"},
         )
-        response.raise_for_status()
-        # check resulting status code
-        root = lxml_parse_string(response.content)
-        status_code = root.find(
-            ".//{http://exacttarget.com/wsdl/partnerAPI}StatusCode"
-        ).text
-        status_message = root.find(
-            ".//{http://exacttarget.com/wsdl/partnerAPI}StatusMessage"
-        ).text
-        success = True
-        if status_code == "OK":
-            user_name = self.options.get("user_name")
-            self.logger.info(f"Successfully updated role for User: {user_name}.")
-        if status_code != "OK":
-            raise Exception(
-                f"Error from Marketing Cloud: {status_message}\n\nFull response text: {response.text}"
-            )
-        self.return_values = {"success": success}
+        self._check_soap_response(response)
+        user_name = self.options.get("user_name")
+        self.logger.info(f"Successfully updated role for User: {user_name}.")
+        self.return_values = {"success": True}
diff --git a/cumulusci/tasks/marketing_cloud/base.py b/cumulusci/tasks/marketing_cloud/base.py
index 3387e046b..fd1228a78 100644
--- a/cumulusci/tasks/marketing_cloud/base.py
+++ b/cumulusci/tasks/marketing_cloud/base.py
@@ -1,4 +1,5 @@
 from cumulusci.core.tasks import BaseTask
+from cumulusci.utils.xml import lxml_parse_string
 
 
 class BaseMarketingCloudTask(BaseTask):
@@ -15,3 +16,18 @@ class BaseMarketingCloudTask(BaseTask):
     def _init_task(self):
         super()._init_task()
         self.mc_config = self.project_config.keychain.get_service("marketing_cloud")
+
+    def _check_soap_response(self, response):
+        """Make sure the response indicates success."""
+        response.raise_for_status()
+        root = lxml_parse_string(response.content)
+        status_code = root.find(
+            ".//{http://exacttarget.com/wsdl/partnerAPI}StatusCode"
+        ).text
+        status_message = root.find(
+            ".//{http://exacttarget.com/wsdl/partnerAPI}StatusMessage"
+        ).text
+        if status_code != "OK":
+            raise Exception(
+                f"Error from Marketing Cloud: {status_message}\n\nFull response text: {response.text}"
+            )


Test patch:
diff --git a/cumulusci/tasks/marketing_cloud/tests/test_api.py b/cumulusci/tasks/marketing_cloud/tests/test_api.py
index de84572b1..3cc9761dd 100644
--- a/cumulusci/tasks/marketing_cloud/tests/test_api.py
+++ b/cumulusci/tasks/marketing_cloud/tests/test_api.py
@@ -56,12 +56,17 @@ def test_marketing_cloud_create_user_task(create_task, mc_project_config):
             "user_password": "SterlingCooperDraperPryce1!",
             "user_username": "sterling-don",
             "role_id": "31",
+            "activate_if_existing": "True",
         },
         project_config=mc_project_config,
     )
     task()
     assert task.return_values == {"success": True}
 
+    request = responses.calls[-1].request
+    assert b"<ActiveFlag>true</ActiveFlag>" in request.body
+    assert b"<IsLocked>false</IsLocked>" in request.body
+
 
 @responses.activate
 def test_marketing_cloud_update_user_role_task(create_task, mc_project_config):