def post(self, request, *args, **kwargs): can_toggle_is_staff = request.user.is_staff form = SuperuserManagementForm(can_toggle_is_staff, self.request.POST) if form.is_valid(): users = form.cleaned_data['users'] is_superuser = '******' in form.cleaned_data['privileges'] is_staff = 'is_staff' in form.cleaned_data['privileges'] fields_changed = {} for user in users: # save user object only if needed and just once if user.is_superuser is not is_superuser: user.is_superuser = is_superuser fields_changed['is_superuser'] = is_superuser if can_toggle_is_staff and user.is_staff is not is_staff: user.is_staff = is_staff fields_changed['is_staff'] = is_staff if fields_changed: user.save() couch_user = CouchUser.from_django_user(user) log_user_change(by_domain=None, for_domain=None, couch_user=couch_user, changed_by_user=self.request.couch_user, changed_via=USER_CHANGE_VIA_WEB, fields_changed=fields_changed, by_domain_required_for_log=False, for_domain_required_for_log=False) messages.success(request, _("Successfully updated superuser permissions")) return self.get(request, *args, **kwargs)
def log_commcare_user_locations_changes(request, user, old_location_id, old_assigned_location_ids): change_messages = {} fields_changed = {} if old_location_id != user.location_id: location = None fields_changed['location_id'] = user.location_id if user.location_id: location = SQLLocation.objects.get(location_id=user.location_id) change_messages.update( UserChangeMessage.primary_location_info(location)) if old_assigned_location_ids != user.assigned_location_ids: locations = [] fields_changed['assigned_location_ids'] = user.assigned_location_ids if user.assigned_location_ids: locations = SQLLocation.objects.filter( location_id__in=user.assigned_location_ids) change_messages.update( UserChangeMessage.assigned_locations_info(locations)) if change_messages: log_user_change(by_domain=request.domain, for_domain=user.domain, couch_user=user, changed_by_user=request.couch_user, changed_via=USER_CHANGE_VIA_WEB, fields_changed=fields_changed, change_messages=change_messages)
def post(self, request, *args, **kwargs): if self.request.couch_user.is_domain_admin(self.domain_to_remove): messages.error( request, _("Unable remove membership because you are the admin of %s") % self.domain_to_remove) else: try: self.request.couch_user.delete_domain_membership( self.domain_to_remove, create_record=True) self.request.couch_user.save() log_user_change( by_domain=None, for_domain=self.domain_to_remove, couch_user=request.couch_user, changed_by_user=request.couch_user, changed_via=USER_CHANGE_VIA_WEB, change_messages=UserChangeMessage.domain_removal( self.domain_to_remove), by_domain_required_for_log=False, ) messages.success( request, _("You are no longer part of the project %s") % self.domain_to_remove) except Exception: messages.error( request, _("There was an error removing you from this project.")) return self.get(request, *args, **kwargs)
def process_add_phone_number(self): if self.phone_number_is_valid(): user = self.request.couch_user is_new_phone_number = self.phone_number not in user.phone_numbers user.add_phone_number(self.phone_number) user.save() if is_new_phone_number: log_user_change( by_domain=None, for_domain=None, couch_user=user, changed_by_user=user, changed_via=USER_CHANGE_VIA_WEB, change_messages=UserChangeMessage.phone_numbers_added( [self.phone_number]), by_domain_required_for_log=False, for_domain_required_for_log=False, ) messages.success(self.request, _("Phone number added.")) else: messages.error( self.request, _("Invalid phone number format entered. " "Please enter number, including country code, in digits only." )) return HttpResponseRedirect(reverse(MyAccountSettingsView.urlname))
def _log_web_user_membership_removed(user, domain, via): log_user_change(by_domain=None, for_domain=domain, couch_user=user, changed_by_user=SYSTEM_USER_ID, changed_via=via, change_messages=UserChangeMessage.domain_removal(domain))
def form_valid(self, form): change_messages = {} if not self.user: return self.redirect_response(self.request) reset_password = form.cleaned_data['reset_password'] if reset_password: self.user.set_password(uuid.uuid4().hex) change_messages.update(UserChangeMessage.password_reset()) # toggle active state self.user.is_active = not self.user.is_active self.user.save() verb = 're-enabled' if self.user.is_active else 'disabled' reason = form.cleaned_data['reason'] change_messages.update( UserChangeMessage.status_update(self.user.is_active, reason)) couch_user = CouchUser.from_django_user(self.user) log_user_change(by_domain=None, for_domain=None, couch_user=couch_user, changed_by_user=self.request.couch_user, changed_via=USER_CHANGE_VIA_WEB, change_messages=change_messages, fields_changed={'is_active': self.user.is_active}, by_domain_required_for_log=False, for_domain_required_for_log=False) mail_admins( "User account {}".format(verb), "The following user account has been {verb}: \n" " Account: {username}\n" " Reset by: {reset_by}\n" " Password reset: {password_reset}\n" " Reason: {reason}".format( verb=verb, username=self.username, reset_by=self.request.user.username, password_reset=str(reset_password), reason=reason, )) send_HTML_email( "%sYour account has been %s" % (settings.EMAIL_SUBJECT_PREFIX, verb), self.user.get_email() if self.user else self.username, render_to_string('hqadmin/email/account_disabled_email.html', context={ 'support_email': settings.SUPPORT_EMAIL, 'password_reset': reset_password, 'user': self.user, 'verb': verb, 'reason': form.cleaned_data['reason'], }), ) messages.success(self.request, _('Account successfully %(verb)s.' % {'verb': verb})) return redirect(self.redirect_url)
def test_missing_for_domain(self): with self.assertRaisesMessage(ValueError, "missing 'for_domain' argument'"): log_user_change( by_domain=self.commcare_user.domain, for_domain=None, couch_user=self.commcare_user, changed_by_user=self.web_user, changed_via=USER_CHANGE_VIA_WEB, action=UserModelAction.UPDATE, )
def log_user_groups_change(domain, request, user, group_ids=None): groups = [] # no groups assigned would be group ids as [] # so if group ids were NOT passed or if some were passed, get groups for user if group_ids is None or group_ids: groups = Group.by_user_id(user.get_id) log_user_change( by_domain=domain, for_domain=domain, # Groups are bound to a domain, so use domain couch_user=user, changed_by_user=request.couch_user, changed_via=USER_CHANGE_VIA_WEB, change_messages=UserChangeMessage.groups_info(groups))
def process_delete_phone_number(self): self.request.couch_user.delete_phone_number(self.phone_number) log_user_change( by_domain=None, for_domain=None, couch_user=self.request.couch_user, changed_by_user=self.request.couch_user, changed_via=USER_CHANGE_VIA_WEB, change_messages=UserChangeMessage.phone_numbers_removed( [self.phone_number]), by_domain_required_for_log=False, for_domain_required_for_log=False, ) messages.success(self.request, _("Phone number deleted.")) return HttpResponseRedirect(reverse(MyAccountSettingsView.urlname))
def transfer_domain(self, by_user, *args, transfer_via=None, **kwargs): self.confirm_time = datetime.utcnow() if 'ip' in kwargs: self.confirm_ip = kwargs['ip'] self.from_user.transfer_domain_membership(self.domain, self.to_user, is_admin=True) self.from_user.save() if by_user: log_user_change(by_domain=self.domain, for_domain=self.domain, couch_user=self.from_user, changed_by_user=by_user, changed_via=transfer_via, change_messages=UserChangeMessage.domain_removal( self.domain)) log_user_change(by_domain=self.domain, for_domain=self.domain, couch_user=self.to_user, changed_by_user=by_user, changed_via=transfer_via, change_messages=UserChangeMessage.domain_addition( self.domain)) self.to_user.save() self.active = False self.save() html_content = render_to_string( "{template}.html".format(template=self.DIMAGI_CONFIRM_EMAIL), self.as_dict()) text_content = render_to_string( "{template}.txt".format(template=self.DIMAGI_CONFIRM_EMAIL), self.as_dict()) send_html_email_async.delay( _('There has been a transfer of ownership of {domain}').format( domain=self.domain), settings.SUPPORT_EMAIL, html_content, text_content=text_content, )
def save_only_group_changes(self, group_change_message): return log_user_change( by_domain=self.upload_domain, for_domain=self.user_domain, couch_user=self.user, changed_by_user=self.changed_by_user, changed_via=self.changed_via, change_messages=group_change_message, action=UserModelAction.UPDATE, bulk_upload_record_id=self.upload_record_id, for_domain_required_for_log=self.user_domain_required_for_log, )
def save(self): if self.is_new_user or self._save: action = UserModelAction.CREATE if self.is_new_user else UserModelAction.UPDATE fields_changed = None if self.is_new_user else self.fields_changed return log_user_change( by_domain=self.upload_domain, for_domain=self.user_domain, couch_user=self.user, changed_by_user=self.changed_by_user, changed_via=self.changed_via, change_messages=self.change_messages, action=action, fields_changed=fields_changed, bulk_upload_record_id=self.upload_record_id, for_domain_required_for_log=self.user_domain_required_for_log, )
def test_update(self): restore_phone_numbers_to = self.commcare_user.to_json( )['phone_numbers'] self.commcare_user.add_phone_number("9999999999") change_messages = UserChangeMessage.phone_numbers_added(["9999999999"]) user_history = log_user_change( self.domain, self.domain, self.commcare_user, self.web_user, changed_via=USER_CHANGE_VIA_BULK_IMPORTER, change_messages=change_messages, fields_changed={ 'phone_numbers': self.commcare_user.phone_numbers, 'password': '******' }, action=UserModelAction.UPDATE) self.assertEqual(user_history.by_domain, self.domain) self.assertEqual(user_history.for_domain, self.domain) self.assertEqual(user_history.user_type, "CommCareUser") self.assertIsNotNone(user_history.user_id) self.assertEqual(user_history.user_id, self.commcare_user.get_id) self.assertIsNotNone(user_history.changed_by) self.assertEqual(user_history.changed_by, self.web_user.get_id) self.assertEqual(user_history.changes, {'phone_numbers': ['9999999999']}) self.assertEqual(user_history.changed_via, USER_CHANGE_VIA_BULK_IMPORTER) self.assertEqual(user_history.change_messages, change_messages) self.assertEqual(user_history.action, UserModelAction.UPDATE.value) self.assertEqual(user_history.user_repr, user_id_to_username(self.commcare_user.get_id)) self.assertEqual(user_history.changed_by_repr, user_id_to_username(self.web_user.get_id)) self.commcare_user.phone_numbers = restore_phone_numbers_to
def __call__(self, request, uuid, **kwargs): # add the correct parameters to this instance self.request = request if 'domain' in kwargs: self.domain = kwargs['domain'] if request.GET.get('switch') == 'true': logout(request) return redirect_to_login(request.path) if request.GET.get('create') == 'true': logout(request) return HttpResponseRedirect(request.path) try: invitation = Invitation.objects.get(uuid=uuid) except (Invitation.DoesNotExist, ValidationError): messages.error( request, _("Sorry, it looks like your invitation has expired. " "Please check the invitation link you received and try again, or " "request a project administrator to send you the invitation again." )) return HttpResponseRedirect(reverse("login")) if invitation.is_accepted: messages.error( request, _("Sorry, that invitation has already been used up. " "If you feel this is a mistake please ask the inviter for " "another invitation.")) return HttpResponseRedirect(reverse("login")) self.validate_invitation(invitation) if invitation.is_expired: return HttpResponseRedirect(reverse("no_permissions")) # Add zero-width space to username for better line breaking username = self.request.user.username.replace("@", "​@") context = { 'formatted_username': username, 'domain': self.domain, 'invite_to': self.domain, 'invite_type': _('Project'), 'hide_password_feedback': has_custom_clean_password(), } if request.user.is_authenticated: context['current_page'] = {'page_name': _('Project Invitation')} else: context['current_page'] = { 'page_name': _('Project Invitation, Account Required') } if request.user.is_authenticated: is_invited_user = request.couch_user.username.lower( ) == invitation.email.lower() if self.is_invited(invitation, request.couch_user ) and not request.couch_user.is_superuser: if is_invited_user: # if this invite was actually for this user, just mark it accepted messages.info( request, _("You are already a member of {entity}.").format( entity=self.inviting_entity)) invitation.is_accepted = True invitation.save() else: messages.error( request, _("It looks like you are trying to accept an invitation for " "{invited} but you are already a member of {entity} with the " "account {current}. Please sign out to accept this invitation " "as another user.").format( entity=self.inviting_entity, invited=invitation.email, current=request.couch_user.username)) return HttpResponseRedirect( self.redirect_to_on_success(invitation.email, self.domain)) if not is_invited_user: messages.error( request, _("The invited user {invited} and your user {current} " "do not match!").format( invited=invitation.email, current=request.couch_user.username)) if request.method == "POST": couch_user = CouchUser.from_django_user(request.user, strict=True) invitation.accept_invitation_and_join_domain(couch_user) log_user_change( by_domain=invitation.domain, for_domain=invitation.domain, couch_user=couch_user, changed_by_user=CouchUser.get_by_user_id( invitation.invited_by), changed_via=USER_CHANGE_VIA_INVITATION, change_messages=UserChangeMessage.domain_addition( invitation.domain)) track_workflow( request.couch_user.get_email(), "Current user accepted a project invitation", {"Current user accepted a project invitation": "yes"}) send_hubspot_form(HUBSPOT_EXISTING_USER_INVITE_FORM, request) return HttpResponseRedirect( self.redirect_to_on_success(invitation.email, self.domain)) else: mobile_user = CouchUser.from_django_user( request.user).is_commcare_user() context.update({ 'mobile_user': mobile_user, "invited_user": invitation.email if request.couch_user.username != invitation.email else "", }) return render(request, self.template, context) else: idp = None if settings.ENFORCE_SSO_LOGIN: idp = IdentityProvider.get_active_identity_provider_by_username( invitation.email) if request.method == "POST": form = WebUserInvitationForm(request.POST, is_sso=idp is not None) if form.is_valid(): # create the new user invited_by_user = CouchUser.get_by_user_id( invitation.invited_by) if idp: signup_request = AsyncSignupRequest.create_from_invitation( invitation) return HttpResponseRedirect( idp.get_login_url(signup_request.username)) user = activate_new_user_via_reg_form( form, created_by=invited_by_user, created_via=USER_CHANGE_VIA_INVITATION, domain=invitation.domain, is_domain_admin=False, ) user.save() messages.success( request, _("User account for %s created!") % form.cleaned_data["email"]) invitation.accept_invitation_and_join_domain(user) messages.success( self.request, _('You have been added to the "{}" project space.'). format(self.domain)) authenticated = authenticate( username=form.cleaned_data["email"], password=form.cleaned_data["password"], request=request) if authenticated is not None and authenticated.is_active: login(request, authenticated) track_workflow( request.POST['email'], "New User Accepted a project invitation", {"New User Accepted a project invitation": "yes"}) send_hubspot_form(HUBSPOT_NEW_USER_INVITE_FORM, request, user) return HttpResponseRedirect( self.redirect_to_on_success(invitation.email, invitation.domain)) else: if (CouchUser.get_by_username(invitation.email) or User.objects.filter( username__iexact=invitation.email).count() > 0): login_url = reverse("login") accept_invitation_url = reverse( 'domain_accept_invitation', args=[invitation.domain, invitation.uuid]) return HttpResponseRedirect( f"{login_url}" f"?next={accept_invitation_url}" f"&username={invitation.email}") form = WebUserInvitationForm( initial={ 'email': invitation.email, }, is_sso=idp is not None, ) context.update({ 'is_sso': idp is not None, 'idp_name': idp.name if idp else None, 'invited_user': invitation.email, }) context.update({"form": form}) return render(request, self.template, context)
def form_valid(self, form): from django_otp import devices_for_user username = form.cleaned_data['username'] user = User.objects.get(username__iexact=username) for device in devices_for_user(user): device.delete() couch_user = CouchUser.from_django_user(user) disable_for_days = form.cleaned_data['disable_for_days'] if disable_for_days: disable_until = datetime.utcnow() + timedelta( days=disable_for_days) couch_user.two_factor_auth_disabled_until = disable_until couch_user.save() verification = form.cleaned_data['verification_mode'] verified_by = form.cleaned_data['via_who'] or self.request.user.username change_messages = UserChangeMessage.two_factor_disabled_with_verification( verified_by, verification, disable_for_days) log_user_change(by_domain=None, for_domain=None, couch_user=couch_user, changed_by_user=self.request.couch_user, changed_via=USER_CHANGE_VIA_WEB, change_messages=change_messages, by_domain_required_for_log=False, for_domain_required_for_log=False) mail_admins( "Two-Factor account reset", "Two-Factor auth was reset. Details: \n" " Account reset: {username}\n" " Reset by: {reset_by}\n" " Request Verification Mode: {verification}\n" " Verified by: {verified_by}\n" " Two-Factor disabled for {days} days.".format( username=username, reset_by=self.request.user.username, verification=verification, verified_by=verified_by, days=disable_for_days), ) send_HTML_email( "%sTwo-Factor authentication reset" % settings.EMAIL_SUBJECT_PREFIX, couch_user.get_email(), render_to_string( 'hqadmin/email/two_factor_reset_email.html', context={ 'until': disable_until.strftime('%Y-%m-%d %H:%M:%S UTC') if disable_for_days else None, 'support_email': settings.SUPPORT_EMAIL, 'email_subject': "[URGENT] Possible Account Breach", 'email_body': "Two Factor Auth on my CommCare account " "was disabled without my request. My username is: %s" % username, }), ) messages.success(self.request, _('Two-Factor Auth successfully disabled.')) return redirect('{}?q={}'.format(reverse('web_user_lookup'), username))