-
-
Notifications
You must be signed in to change notification settings - Fork 218
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
base: master
Are you sure you want to change the base?
Conversation
messages.error(request, "The user is already confirmed or is not a mobile user") | ||
return HttpResponse(status=400) | ||
|
||
invite_code = encrypt_account_confirmation_info(user) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
general idea looks good. added a few questions and suggestions
@@ -832,10 +840,15 @@ def __init__(self, project, request_user, *args, **kwargs): | |||
data_bind='value: send_account_confirmation_email', | |||
) | |||
|
|||
if TWO_STAGE_USER_PROVISIONING_BY_SMS.enabled(self.domain): | |||
# cid => connect-id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: might be better to just have connect_id
in the variable than use cid
and add a comment to explain it
) or toggles.TWO_STAGE_USER_PROVISIONING_BY_SMS.enabled(self.domain) | ||
return (toggles.TWO_STAGE_USER_PROVISIONING.enabled(self.domain) | ||
or toggles.TWO_STAGE_USER_PROVISIONING_BY_SMS.enabled(self.domain) | ||
or toggles.COMMCARE_CONNECT.enabled(self.domain)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this require stage confirmation for connect domains or just allow it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a context variable that's used in the template for things that are common for all two-stage provision workflows.
@require_can_edit_commcare_users | ||
@require_POST | ||
@location_safe | ||
def activate_connectid_link(request, domain, user_id): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: if it is a POST
anyway these views can be combined and just pass the is_active
value in the payload, but definitely not blocking and fine to keep it this way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. But there is a tradeoff on JS side. The corresponding JS here which is also used for regular (de/)activate needs to handle the POST param for connectid separately. So it's not much worth.
|
||
def _toggle_connectid_link(request, domain, user_id, is_active): | ||
if not toggles.COMMCARE_CONNECT.enabled(domain): | ||
return HttpResponseBadRequest() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: i think this should be a 403 and can be added as a permission check to the views (the request isn't bad, they just aren't allowed to do it)
def _toggle_connectid_link(request, domain, user_id, is_active): | ||
if not toggles.COMMCARE_CONNECT.enabled(domain): | ||
return HttpResponseBadRequest() | ||
user = CommCareUser.get_by_user_id(user_id, domain) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it possible to pass the username in the view so that we can fetch the django user directly rather than the couch user and then the django user?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tradeoff is that we lookup and send django user IDs for all users in the paginate_mobile_workers
so that the caller knows the django userid to pass it to this view.
commcare_user__username__in=usernames | ||
).annotate(commcare_username=F("commcare_user__username")).all() | ||
return { | ||
link.commcare_username.split("@")[0]: link |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we have a util for this somewhere? I realize its a small thing, but we do it so much in HQ that I am surprised we dont have a function for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I thought the same, I couldn't find either. Most places do this same thing, and perhaps it wasn't worth a util that has just this one line in it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
corehq.apps.users.util.raw_username
domain: {domain} | ||
invite_code: {invite_code} | ||
url: {url} | ||
Make a POST call to above url with invite_code and your ConnectID token |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we want to send the user a message telling them to make a POST, because they won't know what to do with that info. I think we want to pass the right info to the phone so it can do that automatically (or have a clickable link that will confirm it, but a link that is clickable in the sms but doesn't do the right thing is going to be confusing)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, definitely, this is a ToDo; mobile team has given me the correct format, so I will update this.
messages.error(request, "The user is already confirmed or is not a mobile user") | ||
return HttpResponse(status=400) | ||
|
||
invite_code = encrypt_account_confirmation_info(user) |
There was a problem hiding this comment.
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.
messages.error(request, "The user is already confirmed or is not a mobile user") | ||
return HttpResponse(status=400) | ||
|
||
invite_code = encrypt_account_confirmation_info(user) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
if user.domain != domain: | ||
return HttpResponseBadRequest("Invalid Request") | ||
|
||
connectid_username = get_connectid_userinfo(token) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this confirm that the token was valid at all? What response do we send if it wasn't?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the token is not valid this would not return a username. So right now, this would 500 if the token is not valid. Which is same behaviour as that of link_connectid_user.
4e2231e
to
80a6157
Compare
431d542
to
407303f
Compare
@calellowitz Bumping for review please |
c899c82
to
5b496a5
Compare
@calellowitz Bumping for review. This, as well as the cchq one dimagi/commcare-cloud#6449 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you planning to address the lint errors. Code generally looks fine, but that is generally worth doing. Otherwise one question (I see that one of them was corrected in a later commit). Otherwise, I would feel a bit more comfortable if someone outside connect reviewed this, since I am a bit rusty on hq code, but looks good to me.
|
||
def send_connectid_invite_sms(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") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This lint error looks real
# 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") |
There was a problem hiding this comment.
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?
Product Description
Technical Summary
This provides UI actions to provision users using ConnectID auth backend implemented previously.
Feature Flag
COMMCARE_CONNECT
Safety Assurance
Safety story
I have tested this locally and it's within a feature flag
Automated test coverage
QA Plan
Todo
Migrations
Rollback instructions
Labels & Review