Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ConnectID Invite/De-Link/Re-Link features #34967

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
19 changes: 16 additions & 3 deletions corehq/apps/user_importer/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@
InvitationStatus
)
from corehq.const import USER_CHANGE_VIA_BULK_IMPORTER
from corehq.toggles import DOMAIN_PERMISSIONS_MIRROR, TABLEAU_USER_SYNCING
from corehq.toggles import (
DOMAIN_PERMISSIONS_MIRROR,
TABLEAU_USER_SYNCING,
COMMCARE_CONNECT,
)
from corehq.apps.sms.util import validate_phone_number

from dimagi.utils.logging import notify_error
Expand Down Expand Up @@ -96,6 +100,9 @@ def check_headers(user_specs, domain, is_web_upload=False):
if TABLEAU_USER_SYNCING.enabled(domain):
allowed_headers.update({'tableau_role', 'tableau_groups'})

if COMMCARE_CONNECT.enabled(domain):
allowed_headers.add('send_connectid_invite')

illegal_headers = headers - allowed_headers

if is_web_upload:
Expand Down Expand Up @@ -497,6 +504,9 @@ def _parse_username(self):

def _parse_password(self):
from corehq.apps.user_importer.validation import is_password
if self.column_values["send_connectid_invite"]:
# password login is disabled for connectid
password = ''
if self.row.get('password'):
password = str(self.row.get('password'))
elif self.column_values["send_confirmation_sms"]:
Expand Down Expand Up @@ -530,10 +540,10 @@ def _process_column_values(self):
}

for v in ['is_active', 'is_account_confirmed', 'send_confirmation_email',
'remove_web_user', 'send_confirmation_sms']:
'remove_web_user', 'send_confirmation_sms', 'send_connectid_invite']:
values[v] = spec_value_to_boolean_or_none(self.row, v)

if values["send_confirmation_sms"] and not values["user_id"]:
if (values["send_confirmation_sms"] or values["send_connectid_invite"]) and not values["user_id"]:
values["is_account_confirmed"] = False
else:
values["is_account_confirmed"] = values["is_account_confirmed"]
Expand Down Expand Up @@ -691,6 +701,9 @@ def _process_web_user(self):
send_account_confirmation_if_necessary(self.user)
if cv["send_confirmation_sms"]:
send_account_confirmation_sms_if_necessary(self.user)
elif cv["send_connectid_invite"]:
from corehq.apps.users.views.mobile.users import deliver_connectid_invite
deliver_connectid_invite(self.user)


class WebUserRow(BaseUserRow):
Expand Down
7 changes: 6 additions & 1 deletion corehq/apps/user_importer/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ def get_user_import_validators(domain_obj, all_specs, is_web_user_import, all_us
NewUserPasswordValidator(domain),
PasswordValidator(domain) if validate_passwords else noop,
GroupValidator(domain, allowed_groups),
ConfirmationSmsValidator(domain)
ConfirmationSmsValidator(domain),
ConnectIdInviteValidator(domain)
]


Expand Down Expand Up @@ -455,6 +456,10 @@ def validate_spec(self, spec):
return self.error_existing_user.format(self.confirmation_sms_header, errors_formatted)


class ConnectIdInviteValidator(ConfirmationSmsValidator):
confirmation_sms_header = "send_connectid_invite"


class LocationValidator(ImportValidator):
error_message_user_access = _("Based on your locations you do not have permission to edit this user or user "
"invitation")
Expand Down
8 changes: 4 additions & 4 deletions corehq/apps/users/views/mobile/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ def create_mobile_worker(self, in_data):
if self.new_mobile_worker_form.cleaned_data['account_invite_by_cid']:
phone_number = self.new_mobile_worker_form.cleaned_data['phone_number']
couch_user.set_default_phone_number(phone_number)
deliver_connectid_invite(couch_user)
deliver_connectid_invite(couch_user, self.request)

plan_limit, user_count = Subscription.get_plan_and_user_count_by_domain(self.domain)
check_and_send_limit_email(self.domain, plan_limit, user_count, user_count - 1)
Expand Down Expand Up @@ -1747,7 +1747,6 @@ def link_connectid_user(request, domain):
@require_can_edit_commcare_users
@location_safe
def send_connectid_invite(request, domain, user_id):
# Currently same as what send_confirmation_sms does
user = CommCareUser.get_by_user_id(user_id, domain)
if user.domain != domain:
return HttpResponse(status=400)
Expand All @@ -1757,10 +1756,11 @@ def send_connectid_invite(request, domain, user_id):
return HttpResponse(status=200)


def deliver_connectid_invite(user):
def deliver_connectid_invite(user, request=None):
# ConnectID server delivers the invite to user
if user.is_account_confirmed or not user.is_commcare_user():
messages.error(request, "The user is already confirmed or is not a mobile user")
if request:
messages.error(request, "The user is already confirmed or is not a mobile user")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not want these messages for the import results?

return False

invite_code = encrypt_account_confirmation_info(user)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo; I will need to coordinate with mobile to get a link that opens ConnectID app with the invite intent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One other alternative is to send this as push notification (probably via the connectid server). That way the app is already handling it, and we know they are logged in. It will also allow us to hide all the info that doesn't matter to the user (like the code or username, which they won't be familiar with), since we can pass that in the data params.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, definitely push notification would be better.

I remember Dave mentioning this specifically needed an SMS deeplink (I don't recall why). I will check with him and see if we can do push notification (on top of SMS).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does encrypting the invite code do for us here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the code 'encodes' the invitation sent time, so that before confirming it can check the invite didn't expire. This is really a soft enforcement used in the other two multi-stage user provision workflows, so I took the pattern as it is.

Expand Down