Exemple #1
0
def remove_web_user_from_domain(domain,
                                user,
                                username,
                                upload_user,
                                user_change_logger=None,
                                is_web_upload=False):
    if not user or not user.is_member_of(domain):
        if is_web_upload:
            remove_invited_web_user(domain, username)
            if user_change_logger:
                user_change_logger.add_info(
                    UserChangeMessage.invitation_revoked_for_domain(domain))
        else:
            raise UserUploadError(
                _("You cannot remove a web user that is not a member of this project."
                  " {web_user} is not a member.").format(web_user=user))
    elif username == upload_user.username:
        raise UserUploadError(
            _("You cannot remove yourself from a domain via bulk upload"))
    else:
        user.delete_domain_membership(domain)
        user.save()
        if user_change_logger:
            user_change_logger.add_info(
                UserChangeMessage.domain_removal(domain))
Exemple #2
0
def _get_or_create_commcare_user(domain, user_id, username,
                                 is_account_confirmed, web_user_username,
                                 password, upload_user):
    if user_id:
        user = CommCareUser.get_by_user_id(user_id, domain)
        if not user:
            raise UserUploadError(
                _("User with ID '{user_id}' not found").format(user_id=user_id,
                                                               domain=domain))
        check_changing_username(user, username)

        # note: explicitly not including "None" here because that's the default value if not set.
        # False means it was set explicitly to that value
        if is_account_confirmed is False and not web_user_username:
            raise UserUploadError(
                _("You can only set 'Is Account Confirmed' to 'False' on a new User."
                  ))
    else:
        kwargs = {}
        if is_account_confirmed is not None and not web_user_username:
            kwargs['is_account_confirmed'] = is_account_confirmed
        user = CommCareUser.create(domain,
                                   username,
                                   password,
                                   created_by=upload_user,
                                   created_via=USER_CHANGE_VIA_BULK_IMPORTER,
                                   commit=False,
                                   **kwargs)
    return user
Exemple #3
0
def check_headers(user_specs, domain, is_web_upload=False):
    messages = []
    headers = set(user_specs.fieldnames)

    # Backwards warnings
    for (old_name, new_name) in old_headers.items():
        if old_name in headers:
            messages.append(
                _("'The column header '{old_name}' is deprecated, please use '{new_name}' instead."
                  ).format(old_name=old_name, new_name=new_name))
            headers.discard(old_name)

    if DOMAIN_PERMISSIONS_MIRROR.enabled(domain):
        allowed_headers.add('domain')

    illegal_headers = headers - allowed_headers
    if is_web_upload:
        missing_headers = web_required_headers - headers
    else:
        missing_headers = required_headers - headers

    for header_set, label in (missing_headers, 'required'), (illegal_headers,
                                                             'illegal'):
        if header_set:
            messages.append(
                _('The following are {label} column headers: {headers}.').
                format(label=label, headers=', '.join(header_set)))
    if messages:
        raise UserUploadError('\n'.join(messages))
Exemple #4
0
def get_location_from_site_code(site_code, location_cache):
    if isinstance(site_code, str):
        site_code = site_code.lower()
    elif isinstance(site_code, int):
        site_code = str(site_code)
    else:
        raise UserUploadError(
            _("Unexpected format received for site code '%(site_code)s'") %
            {'site_code': site_code})

    try:
        return location_cache.get(site_code)
    except SQLLocation.DoesNotExist:
        raise UserUploadError(
            _("Could not find organization with site code '%(site_code)s'") %
            {'site_code': site_code})
Exemple #5
0
    def update_user_data(self, data, uncategorized_data, profile, domain_info):
        # Add in existing data. Don't use metadata - we don't want to add profile-controlled fields.
        current_profile_id = self.user.user_data.get(PROFILE_SLUG)

        for key, value in self.user.user_data.items():
            if key not in data:
                data[key] = value
        if profile:
            profile_obj = domain_info.profiles_by_name[profile]
            data[PROFILE_SLUG] = profile_obj.id
            for key in profile_obj.fields.keys():
                self.user.pop_metadata(key)
        try:
            self.user.update_metadata(data)
        except ValueError as e:
            raise UserUploadError(str(e))
        if uncategorized_data:
            self.user.update_metadata(uncategorized_data)

        # Clear blank user data so that it can be purged by remove_unused_custom_fields_from_users_task
        for key in dict(data, **uncategorized_data):
            value = self.user.metadata[key]
            if value is None or value == '':
                self.user.pop_metadata(key)

        new_profile_id = self.user.user_data.get(PROFILE_SLUG)
        if new_profile_id and new_profile_id != current_profile_id:
            profile_name = domain_info.profile_name_by_id[new_profile_id]
            self.logger.add_info(
                UserChangeMessage.profile_info(new_profile_id, profile_name))
Exemple #6
0
def check_changing_username(user, username):
    if username and user.username != username:
        raise UserUploadError(
            _('Changing usernames is not supported: %(username)r to %(new_username)r'
              ) % {
                  'username': user.username,
                  'new_username': username
              })
Exemple #7
0
def remove_web_user_from_domain(domain,
                                user,
                                username,
                                upload_user,
                                is_web_upload=False):
    if not user or not user.is_member_of(domain):
        if is_web_upload:
            remove_invited_web_user(domain, username)
        else:
            raise UserUploadError(
                _("You cannot remove a web user that is not a member of this project."
                  " {web_user} is not a member.").format(web_user=user))
    elif username == upload_user.username:
        raise UserUploadError(
            _("You cannot remove a yourself from a domain via bulk upload"))
    else:
        user.delete_domain_membership(domain)
        user.save()
Exemple #8
0
def remove_invited_web_user(domain, username):
    try:
        invitation = Invitation.objects.get(domain=domain, email=username)
    except Invitation.DoesNotExist:
        raise UserUploadError(
            _("You cannot remove a web user that is not a member or invited to this project. "
              "{username} is not a member or invited.").format(
                  username=username))
    invitation.delete()
def check_headers(user_specs):
    messages = []
    headers = set(user_specs.fieldnames)

    # Backwards warnings
    for (old_name, new_name) in old_headers.items():
        if old_name in headers:
            messages.append(
                _("'The column header '{old_name}' is deprecated, please use '{new_name}' instead.").format(
                    old_name=old_name, new_name=new_name
                ))
            headers.discard(old_name)

    illegal_headers = headers - allowed_headers
    missing_headers = required_headers - headers

    for header_set, label in (missing_headers, 'required'), (illegal_headers, 'illegal'):
        if header_set:
            messages.append(_('The following are {label} column headers: {headers}.').format(
                label=label, headers=', '.join(header_set)))
    if messages:
        raise UserUploadError('\n'.join(messages))
Exemple #10
0
def get_domain_info(domain,
                    upload_domain,
                    user_specs,
                    domain_info_by_domain,
                    upload_user=None,
                    group_memoizer=None,
                    is_web_upload=False):
    from corehq.apps.users.views.mobile.custom_data_fields import UserFieldsView
    from corehq.apps.users.views.utils import get_editable_role_choices

    domain_info = domain_info_by_domain.get(domain)
    if domain_info:
        return domain_info
    if domain == upload_domain:
        domain_group_memoizer = group_memoizer or GroupMemoizer(domain)
    else:
        domain_group_memoizer = GroupMemoizer(domain)
    domain_group_memoizer.load_all()
    can_assign_locations = domain_has_privilege(domain, privileges.LOCATIONS)
    location_cache = None
    if can_assign_locations:
        location_cache = SiteCodeToLocationCache(domain)

    domain_obj = Domain.get_by_name(domain)
    if domain_obj is None:
        raise UserUploadError(
            _("Domain cannot be set to '{domain}'".format(domain=domain)))

    allowed_group_names = [
        group.name for group in domain_group_memoizer.groups
    ]
    profiles_by_name = {}
    profile_name_by_id = {}
    domain_user_specs = [
        spec for spec in user_specs
        if spec.get('domain', upload_domain) == domain
    ]
    if is_web_upload:
        roles_by_name = {
            role[1]: role[0]
            for role in get_editable_role_choices(
                domain, upload_user, allow_admin_role=True)
        }
        validators = get_user_import_validators(
            domain_obj,
            domain_user_specs,
            True,
            allowed_roles=list(roles_by_name),
            upload_domain=upload_domain,
        )
    else:
        roles_by_name = {
            role.name: role.get_qualified_id()
            for role in UserRole.objects.get_by_domain(domain)
        }
        definition = CustomDataFieldsDefinition.get(domain,
                                                    UserFieldsView.field_type)
        if definition:
            profiles = definition.get_profiles()
            profiles_by_name = {profile.name: profile for profile in profiles}
            profile_name_by_id = {
                profile.pk: profile.name
                for profile in profiles
            }
        validators = get_user_import_validators(domain_obj, domain_user_specs,
                                                False, allowed_group_names,
                                                list(roles_by_name),
                                                list(profiles_by_name),
                                                upload_domain)

    domain_info = DomainInfo(validators, can_assign_locations, location_cache,
                             roles_by_name, profiles_by_name,
                             profile_name_by_id, domain_group_memoizer)
    domain_info_by_domain[domain] = domain_info
    return domain_info
Exemple #11
0
def check_user_role(username, role):
    if not role:
        raise UserUploadError(
            _("You cannot upload a web user without a role. {username} does not have "
              "a role").format(username=username))
Exemple #12
0
def check_can_upload_web_users(upload_user):
    if not upload_user.can_edit_web_users():
        raise UserUploadError(
            _("Only users with the edit web users permission can upload web users"
              ))
Exemple #13
0
def create_or_update_commcare_users_and_groups(upload_domain,
                                               user_specs,
                                               upload_user,
                                               upload_record_id,
                                               group_memoizer=None,
                                               update_progress=None):
    """"
    Creates and Updates CommCare Users
    For the associated web user username passed, for each CommCareUser
        if corresponding web user is present
            if web user has confirmed account but not a member of domain
                adds them to the domain with same role and primary location as the CommCareUser
            if already a member of domain
                update their role and primary location to be same as that of the CommCareUser
        else creates or updates user invitation
           sets Invitation with the CommCare user's role and primary location
    All changes to users only, are tracked using UserChangeLogger, as an audit trail.
    """
    from corehq.apps.user_importer.helpers import CommCareUserImporter, WebUserImporter

    domain_info_by_domain = {}

    ret = {"errors": [], "rows": []}
    current = 0
    update_deactivate_after_date = EnterpriseMobileWorkerSettings.is_domain_using_custom_deactivation(
        upload_domain)

    for row in user_specs:
        if update_progress:
            update_progress(current)
            current += 1

        username = row.get('username')
        domain = row.get('domain') or upload_domain
        username = normalize_username(str(username),
                                      domain) if username else None
        status_row = {
            'username': username,
            'row': row,
        }

        try:
            domain_info = get_domain_info(domain, upload_domain, user_specs,
                                          domain_info_by_domain,
                                          group_memoizer)

            for validator in domain_info.validators:
                validator(row)
        except UserUploadError as e:
            status_row['flag'] = str(e)
            ret['rows'].append(status_row)
            continue

        data = row.get('data', {})
        email = row.get('email')
        group_names = list(map(str, row.get('group') or []))
        language = row.get('language')
        name = row.get('name')
        password = row.get('password')
        uncategorized_data = row.get('uncategorized_data', {})
        user_id = row.get('user_id')
        location_codes = row.get('location_code',
                                 []) if 'location_code' in row else None
        location_codes = format_location_codes(location_codes)
        role = row.get('role', None)
        profile = row.get('user_profile', None)
        web_user_username = row.get('web_user')
        phone_numbers = row.get('phone-number',
                                []) if 'phone-number' in row else None

        deactivate_after = row.get(
            'deactivate_after', None) if update_deactivate_after_date else None
        if isinstance(deactivate_after, datetime):
            deactivate_after = deactivate_after.strftime("%m-%Y")
            row['deactivate_after'] = deactivate_after

        try:
            password = str(password) if password else None

            is_active = spec_value_to_boolean_or_none(row, 'is_active')
            is_account_confirmed = spec_value_to_boolean_or_none(
                row, 'is_account_confirmed')
            send_account_confirmation_email = spec_value_to_boolean_or_none(
                row, 'send_confirmation_email')
            remove_web_user = spec_value_to_boolean_or_none(
                row, 'remove_web_user')

            user = _get_or_create_commcare_user(domain, user_id, username,
                                                is_account_confirmed,
                                                web_user_username, password,
                                                upload_user)
            commcare_user_importer = CommCareUserImporter(
                upload_domain,
                domain,
                user,
                upload_user,
                is_new_user=not bool(user_id),
                via=USER_CHANGE_VIA_BULK_IMPORTER,
                upload_record_id=upload_record_id)
            if user_id:
                if is_password(password):
                    commcare_user_importer.update_password(password)
                    # overwrite password in results so we do not save it to the db
                    status_row['row']['password'] = '******'
                status_row['flag'] = 'updated'
            else:
                status_row['flag'] = 'created'

            if phone_numbers is not None:
                phone_numbers = clean_phone_numbers(phone_numbers)
                commcare_user_importer.update_phone_numbers(phone_numbers)

            if name:
                commcare_user_importer.update_name(name)

            commcare_user_importer.update_user_data(data, uncategorized_data,
                                                    profile, domain_info)

            if update_deactivate_after_date:
                commcare_user_importer.update_deactivate_after(
                    deactivate_after)

            if language:
                commcare_user_importer.update_language(language)
            if email:
                commcare_user_importer.update_email(email)
            if is_active is not None:
                commcare_user_importer.update_status(is_active)

            # Do this here so that we validate the location code before we
            # save any other information to the user, this way either all of
            # the user's information is updated, or none of it
            # Do not update location info if the column is not included at all
            if domain_info.can_assign_locations and location_codes is not None:
                commcare_user_importer.update_locations(
                    location_codes, domain_info)

            if role:
                role_qualified_id = domain_info.roles_by_name[role]
                commcare_user_importer.update_role(role_qualified_id)
            elif not commcare_user_importer.logger.is_new_user and 'role' in row:
                commcare_user_importer.update_role('none')

            if web_user_username:
                user.update_metadata({'login_as_user': web_user_username})

            user.save()
            log = commcare_user_importer.save_log()

            if web_user_username:
                check_can_upload_web_users(domain, upload_user)
                web_user = CouchUser.get_by_username(web_user_username)
                if web_user:
                    web_user_importer = WebUserImporter(
                        upload_domain,
                        domain,
                        web_user,
                        upload_user,
                        is_new_user=False,
                        via=USER_CHANGE_VIA_BULK_IMPORTER,
                        upload_record_id=upload_record_id)
                    user_change_logger = web_user_importer.logger
                else:
                    web_user_importer = None
                    user_change_logger = None
                if remove_web_user:
                    remove_web_user_from_domain(domain, web_user, username,
                                                upload_user,
                                                user_change_logger)
                else:
                    check_user_role(username, role)
                    if not web_user and is_account_confirmed:
                        raise UserUploadError(
                            _("You can only set 'Is Account Confirmed' to 'True' on an existing Web User. "
                              f"{web_user_username} is a new username.").
                            format(web_user_username=web_user_username))
                    if web_user and not web_user.is_member_of(
                            domain) and is_account_confirmed:
                        # add confirmed account to domain
                        # role_qualified_id would be be present here as confirmed in check_user_role
                        web_user_importer.add_to_domain(
                            role_qualified_id, user.location_id)
                    elif not web_user or not web_user.is_member_of(domain):
                        create_or_update_web_user_invite(
                            web_user_username,
                            domain,
                            role_qualified_id,
                            upload_user,
                            user.location_id,
                            user_change_logger,
                            send_email=send_account_confirmation_email)
                    elif web_user.is_member_of(domain):
                        # edit existing user in the domain
                        web_user_importer.update_role(role_qualified_id)
                        if location_codes is not None:
                            web_user_importer.update_primary_location(
                                user.location_id)
                        web_user.save()
                if web_user_importer:
                    web_user_importer.save_log()
            if send_account_confirmation_email and not web_user_username:
                send_account_confirmation_if_necessary(user)

            if is_password(password):
                # Without this line, digest auth doesn't work.
                # With this line, digest auth works.
                # Other than that, I'm not sure what's going on
                # Passing use_primary_db=True because of https://dimagi-dev.atlassian.net/browse/ICDS-465
                user.get_django_user(
                    use_primary_db=True).check_password(password)

            group_change_message = commcare_user_importer.update_user_groups(
                domain_info, group_names)

            try:
                domain_info.group_memoizer.save_updated()
            except BulkSaveError as e:
                _error_message = (
                    "Oops! We were not able to save some of your group changes. "
                    "Please make sure no one else is editing your groups "
                    "and try again.")
                logging.exception(('BulkSaveError saving groups. '
                                   'User saw error message "%s". Errors: %s') %
                                  (_error_message, e.errors))
                ret['errors'].append(_error_message)

            if log and group_change_message:
                log.change_messages.update(group_change_message)
                log.save()
            elif group_change_message:
                log = commcare_user_importer.logger.save_only_group_changes(
                    group_change_message)

        except ValidationError as e:
            status_row['flag'] = e.message
        except (UserUploadError, CouchUser.Inconsistent) as e:
            status_row['flag'] = str(e)

        ret["rows"].append(status_row)

    return ret
Exemple #14
0
def create_or_update_web_users(upload_domain,
                               user_specs,
                               upload_user,
                               update_progress=None):
    domain_info_by_domain = {}

    ret = {"errors": [], "rows": []}
    current = 0

    for row in user_specs:
        if update_progress:
            update_progress(current)
            current += 1
        role_updated = False

        username = row.get('username')
        domain = row.get('domain') or upload_domain
        status_row = {
            'username': username,
            'row': row,
        }
        domain_info = get_domain_info(domain,
                                      upload_domain,
                                      user_specs,
                                      domain_info_by_domain,
                                      upload_user=upload_user,
                                      is_web_upload=True)
        try:
            for validator in domain_info.validators:
                validator(row)
        except UserUploadError as e:
            status_row['flag'] = str(e)
            ret['rows'].append(status_row)
            continue

        role = row.get('role', None)
        status = row.get('status')

        location_codes = row.get('location_code',
                                 []) if 'location_code' in row else None
        location_codes = format_location_codes(location_codes)

        try:
            remove = spec_value_to_boolean_or_none(row, 'remove')
            check_user_role(username, role)
            role_qualified_id = domain_info.roles_by_name[role]
            check_can_upload_web_users(upload_user)

            user = CouchUser.get_by_username(username, strict=True)
            if user:
                check_changing_username(user, username)
                if remove:
                    remove_web_user_from_domain(domain,
                                                user,
                                                username,
                                                upload_user,
                                                is_web_upload=True)
                else:
                    membership = user.get_domain_membership(domain)
                    if membership:
                        modify_existing_user_in_domain(domain, domain_info,
                                                       location_codes,
                                                       membership,
                                                       role_qualified_id,
                                                       upload_user, user)
                    else:
                        create_or_update_web_user_invite(
                            username, domain, role_qualified_id, upload_user,
                            user.location_id)
                status_row['flag'] = 'updated'

            else:
                if remove:
                    remove_invited_web_user(domain, username)
                    status_row['flag'] = 'updated'
                else:
                    if status == "Invited":
                        try:
                            invitation = Invitation.objects.get(domain=domain,
                                                                email=username)
                        except Invitation.DoesNotExist:
                            raise UserUploadError(
                                _("You can only set 'Status' to 'Invited' on a pending Web User."
                                  " {web_user} is not yet invited.").format(
                                      web_user=username))
                        if invitation.email_status == InvitationStatus.BOUNCED and invitation.email == username:
                            raise UserUploadError(
                                _("The email has bounced for this user's invite. Please try "
                                  "again with a different username").format(
                                      web_user=username))
                    user_invite_loc_id = None
                    if domain_info.can_assign_locations and location_codes is not None:
                        # set invite location to first item in location_codes
                        if len(location_codes) > 0:
                            user_invite_loc = get_location_from_site_code(
                                location_codes[0], domain_info.location_cache)
                            user_invite_loc_id = user_invite_loc.location_id
                    create_or_update_web_user_invite(username, domain,
                                                     role_qualified_id,
                                                     upload_user,
                                                     user_invite_loc_id)
                    status_row['flag'] = 'invited'

        except (UserUploadError, CouchUser.Inconsistent) as e:
            status_row['flag'] = str(e)

        ret["rows"].append(status_row)

    return ret
Exemple #15
0
 def _update_change_messages(self, change_messages):
     for slug in change_messages:
         if slug in self.change_messages:
             raise UserUploadError(f"Double Entry for {slug}")
     self.change_messages.update(change_messages)
Exemple #16
0
 def __call__(self, spec):
     error_message = self.validate_spec(spec)
     if error_message:
         raise UserUploadError(error_message)
Exemple #17
0
def create_or_update_users_and_groups(upload_domain, user_specs, upload_user, group_memoizer=None, update_progress=None):
    domain_info_by_domain = {}

    def _get_domain_info(domain):
        domain_info = domain_info_by_domain.get(domain)
        if domain_info:
            return domain_info
        if domain == upload_domain:
            domain_group_memoizer = group_memoizer or GroupMemoizer(domain)
        else:
            domain_group_memoizer = GroupMemoizer(domain)
        domain_group_memoizer.load_all()
        can_assign_locations = domain_has_privilege(domain, privileges.LOCATIONS)
        location_cache = None
        if can_assign_locations:
            location_cache = SiteCodeToLocationCache(domain)

        domain_obj = Domain.get_by_name(domain)
        allowed_group_names = [group.name for group in domain_group_memoizer.groups]
        roles_by_name = {role.name: role for role in UserRole.by_domain(domain)}
        domain_user_specs = [spec for spec in user_specs if spec.get('domain', upload_domain) == domain]
        validators = get_user_import_validators(
            domain_obj,
            domain_user_specs,
            allowed_group_names,
            list(roles_by_name),
            upload_domain
        )

        domain_info = DomainInfo(
            validators,
            can_assign_locations,
            location_cache,
            roles_by_name,
            domain_group_memoizer
        )
        domain_info_by_domain[domain] = domain_info
        return domain_info

    ret = {"errors": [], "rows": []}

    current = 0

    try:
        for row in user_specs:
            if update_progress:
                update_progress(current)
                current += 1

            username = row.get('username')
            domain = row.get('domain') or upload_domain
            username = normalize_username(str(username), domain) if username else None
            status_row = {
                'username': username,
                'row': row,
            }

            domain_info = _get_domain_info(domain)

            try:
                for validator in domain_info.validators:
                    validator(row)
            except UserUploadError as e:
                status_row['flag'] = str(e)
                ret['rows'].append(status_row)
                continue

            data = row.get('data')
            email = row.get('email')
            group_names = list(map(str, row.get('group') or []))
            language = row.get('language')
            name = row.get('name')
            password = row.get('password')
            phone_number = row.get('phone-number')
            uncategorized_data = row.get('uncategorized_data')
            user_id = row.get('user_id')
            location_codes = row.get('location_code') or []
            if location_codes and not isinstance(location_codes, list):
                location_codes = [location_codes]
            # ignore empty
            location_codes = [code for code in location_codes if code]
            role = row.get('role', None)
            web_user = row.get('web_user')

            try:
                password = str(password) if password else None

                is_active = spec_value_to_boolean_or_none(row, 'is_active')
                is_account_confirmed = spec_value_to_boolean_or_none(row, 'is_account_confirmed')
                send_account_confirmation_email = spec_value_to_boolean_or_none(row, 'send_confirmation_email')
                remove_web_user = spec_value_to_boolean_or_none(row, 'remove_web_user')

                if user_id:
                    user = CommCareUser.get_by_user_id(user_id, domain)
                    if not user:
                        raise UserUploadError(_(
                            "User with ID '{user_id}' not found"
                        ).format(user_id=user_id, domain=domain))

                    if username and user.username != username:
                        raise UserUploadError(_(
                            'Changing usernames is not supported: %(username)r to %(new_username)r'
                        ) % {'username': user.username, 'new_username': username})

                    # note: explicitly not including "None" here because that's the default value if not set.
                    # False means it was set explicitly to that value
                    if is_account_confirmed is False and not web_user:
                        raise UserUploadError(_(
                            "You can only set 'Is Account Confirmed' to 'False' on a new User."
                        ))

                    if is_password(password):
                        user.set_password(password)
                        # overwrite password in results so we do not save it to the db
                        status_row['row']['password'] = '******'
                    status_row['flag'] = 'updated'
                else:
                    kwargs = {}
                    if is_account_confirmed is not None and not web_user:
                        kwargs['is_account_confirmed'] = is_account_confirmed
                    user = CommCareUser.create(domain, username, password, created_by=upload_user,
                                               created_via=USER_CHANGE_VIA_BULK_IMPORTER, commit=False, **kwargs)
                    status_row['flag'] = 'created'

                if phone_number:
                    user.add_phone_number(_fmt_phone(phone_number), default=True)
                if name:
                    user.set_full_name(str(name))
                if data:
                    user.user_data.update(data)
                if uncategorized_data:
                    user.user_data.update(uncategorized_data)
                if language:
                    user.language = language
                if email:
                    user.email = email.lower()
                if is_active is not None:
                    user.is_active = is_active

                if domain_info.can_assign_locations:
                    # Do this here so that we validate the location code before we
                    # save any other information to the user, this way either all of
                    # the user's information is updated, or none of it
                    location_ids = []
                    for code in location_codes:
                        loc = get_location_from_site_code(code, domain_info.location_cache)
                        location_ids.append(loc.location_id)

                    locations_updated = set(user.assigned_location_ids) != set(location_ids)
                    primary_location_removed = (user.location_id and not location_ids or
                                                user.location_id not in location_ids)

                    if primary_location_removed:
                        user.unset_location(commit=False)
                    if locations_updated:
                        user.reset_locations(location_ids, commit=False)

                if role:
                    role_qualified_id = domain_info.roles_by_name[role].get_qualified_id()
                    user.set_role(domain, role_qualified_id)

                if web_user:
                    user.user_data.update({'login_as_user': web_user})

                user.save()

                if web_user:
                    if not upload_user.can_edit_web_users():
                        raise UserUploadError(_(
                            "Only users with the edit web users permission can upload web users"
                        ))
                    current_user = CouchUser.get_by_username(web_user)
                    if remove_web_user:
                        if not current_user or not current_user.is_member_of(domain):
                            raise UserUploadError(_(
                                "You cannot remove a web user that is not a member of this project. {web_user} is not a member.").format(web_user=web_user)
                            )
                        else:
                            current_user.delete_domain_membership(domain)
                            current_user.save()
                    else:
                        if not role:
                            raise UserUploadError(_(
                                "You cannot upload a web user without a role. {web_user} does not have a role").format(web_user=web_user)
                            )
                        if not current_user and is_account_confirmed:
                            raise UserUploadError(_(
                                "You can only set 'Is Account Confirmed' to 'True' on an existing Web User. {web_user} is a new username.").format(web_user=web_user)
                            )
                        if current_user and not current_user.is_member_of(domain) and is_account_confirmed:
                            current_user.add_as_web_user(domain, role=role_qualified_id, location_id=user.location_id)
                        elif not current_user or not current_user.is_member_of(domain):
                            invite_data = {
                                'email': web_user,
                                'invited_by': upload_user.user_id,
                                'invited_on': datetime.utcnow(),
                                'domain': domain,
                                'role': role_qualified_id,
                                'supply_point': user.location_id
                            }
                            invite = Invitation(**invite_data)
                            invite.save()
                            if send_account_confirmation_email:
                                invite.send_activation_email()

                        elif current_user.is_member_of(domain):
                            # edit existing user in the domain
                            current_user.set_role(domain, role_qualified_id)
                            if user.location_id:
                                current_user.set_location(domain, user.location_id)
                            else:
                                current_user.unset_location(domain)
                            current_user.save()

                if send_account_confirmation_email and not web_user:
                    send_account_confirmation_if_necessary(user)

                if is_password(password):
                    # Without this line, digest auth doesn't work.
                    # With this line, digest auth works.
                    # Other than that, I'm not sure what's going on
                    # Passing use_primary_db=True because of https://dimagi-dev.atlassian.net/browse/ICDS-465
                    user.get_django_user(use_primary_db=True).check_password(password)

                for group in domain_info.group_memoizer.by_user_id(user.user_id):
                    if group.name not in group_names:
                        group.remove_user(user)

                for group_name in group_names:
                    domain_info.group_memoizer.by_name(group_name).add_user(user, save=False)

            except (UserUploadError, CouchUser.Inconsistent) as e:
                status_row['flag'] = str(e)

            ret["rows"].append(status_row)
    finally:
        try:
            for domain_info in domain_info_by_domain.values():
                domain_info.group_memoizer.save_all()
        except BulkSaveError as e:
            _error_message = (
                "Oops! We were not able to save some of your group changes. "
                "Please make sure no one else is editing your groups "
                "and try again."
            )
            logging.exception((
                'BulkSaveError saving groups. '
                'User saw error message "%s". Errors: %s'
            ) % (_error_message, e.errors))
            ret['errors'].append(_error_message)

    return ret
Exemple #18
0
def create_or_update_users_and_groups(domain,
                                      user_specs,
                                      group_memoizer=None,
                                      update_progress=None):
    ret = {"errors": [], "rows": []}

    group_memoizer = group_memoizer or GroupMemoizer(domain)
    group_memoizer.load_all()

    current = 0

    can_assign_locations = domain_has_privilege(domain, privileges.LOCATIONS)
    if can_assign_locations:
        location_cache = SiteCodeToLocationCache(domain)

    domain_obj = Domain.get_by_name(domain)
    allowed_group_names = [group.name for group in group_memoizer.groups]
    roles_by_name = {role.name: role for role in UserRole.by_domain(domain)}
    validators = get_user_import_validators(domain_obj, user_specs,
                                            allowed_group_names,
                                            list(roles_by_name))
    try:
        for row in user_specs:
            if update_progress:
                update_progress(current)
                current += 1

            username = row.get('username')
            status_row = {
                'username': username,
                'row': row,
            }

            try:
                for validator in validators:
                    validator(row)
            except UserUploadError as e:
                status_row['flag'] = str(e)
                ret['rows'].append(status_row)
                continue

            data = row.get('data')
            email = row.get('email')
            group_names = list(map(str, row.get('group') or []))
            language = row.get('language')
            name = row.get('name')
            password = row.get('password')
            phone_number = row.get('phone-number')
            uncategorized_data = row.get('uncategorized_data')
            user_id = row.get('user_id')
            location_codes = row.get('location_code') or []
            if location_codes and not isinstance(location_codes, list):
                location_codes = [location_codes]
            # ignore empty
            location_codes = [code for code in location_codes if code]
            role = row.get('role', '')

            try:
                username = normalize_username(str(username),
                                              domain) if username else None
                password = str(password) if password else None

                is_active = spec_value_to_boolean_or_none(row, 'is_active')
                is_account_confirmed = spec_value_to_boolean_or_none(
                    row, 'is_account_confirmed')

                if user_id:
                    user = CommCareUser.get_by_user_id(user_id, domain)
                    if not user:
                        raise UserUploadError(
                            _("User with ID '{user_id}' not found").format(
                                user_id=user_id, domain=domain))

                    if username and user.username != username:
                        raise UserUploadError(
                            _('Changing usernames is not supported: %(username)r to %(new_username)r'
                              ) % {
                                  'username': user.username,
                                  'new_username': username
                              })

                    # note: explicitly not including "None" here because that's the default value if not set.
                    # False means it was set explicitly to that value
                    if is_account_confirmed is False:
                        raise UserUploadError(
                            _(f"You can only set 'Is Account Confirmed' to 'False' on a new User."
                              ))

                    if is_password(password):
                        user.set_password(password)
                    status_row['flag'] = 'updated'
                else:
                    kwargs = {}
                    if is_account_confirmed is not None:
                        kwargs['is_account_confirmed'] = is_account_confirmed
                    user = CommCareUser.create(domain,
                                               username,
                                               password,
                                               commit=False,
                                               **kwargs)
                    status_row['flag'] = 'created'

                if phone_number:
                    user.add_phone_number(_fmt_phone(phone_number),
                                          default=True)
                if name:
                    user.set_full_name(str(name))
                if data:
                    user.user_data.update(data)
                if uncategorized_data:
                    user.user_data.update(uncategorized_data)
                if language:
                    user.language = language
                if email:
                    user.email = email.lower()
                if is_active is not None:
                    user.is_active = is_active

                if can_assign_locations:
                    # Do this here so that we validate the location code before we
                    # save any other information to the user, this way either all of
                    # the user's information is updated, or none of it
                    location_ids = []
                    for code in location_codes:
                        loc = get_location_from_site_code(code, location_cache)
                        location_ids.append(loc.location_id)

                    locations_updated = set(
                        user.assigned_location_ids) != set(location_ids)
                    primary_location_removed = (
                        user.location_id and not location_ids
                        or user.location_id not in location_ids)

                    if primary_location_removed:
                        user.unset_location(commit=False)
                    if locations_updated:
                        user.reset_locations(location_ids, commit=False)

                if role:
                    user.set_role(domain,
                                  roles_by_name[role].get_qualified_id())

                user.save()

                if is_password(password):
                    # Without this line, digest auth doesn't work.
                    # With this line, digest auth works.
                    # Other than that, I'm not sure what's going on
                    # Passing use_primary_db=True because of https://dimagi-dev.atlassian.net/browse/ICDS-465
                    user.get_django_user(
                        use_primary_db=True).check_password(password)

                for group in group_memoizer.by_user_id(user.user_id):
                    if group.name not in group_names:
                        group.remove_user(user)

                for group_name in group_names:
                    group_memoizer.by_name(group_name).add_user(user,
                                                                save=False)

            except (UserUploadError, CouchUser.Inconsistent) as e:
                status_row['flag'] = str(e)

            ret["rows"].append(status_row)
    finally:
        try:
            group_memoizer.save_all()
        except BulkSaveError as e:
            _error_message = (
                "Oops! We were not able to save some of your group changes. "
                "Please make sure no one else is editing your groups "
                "and try again.")
            logging.exception(('BulkSaveError saving groups. '
                               'User saw error message "%s". Errors: %s') %
                              (_error_message, e.errors))
            ret['errors'].append(_error_message)

    return ret
Exemple #19
0
def create_or_update_users_and_groups(upload_domain,
                                      user_specs,
                                      upload_user,
                                      group_memoizer=None,
                                      update_progress=None):
    domain_info_by_domain = {}

    ret = {"errors": [], "rows": []}

    current = 0

    try:
        for row in user_specs:
            if update_progress:
                update_progress(current)
                current += 1
            log_user_create = False
            log_role_update = False

            username = row.get('username')
            domain = row.get('domain') or upload_domain
            username = normalize_username(str(username),
                                          domain) if username else None
            status_row = {
                'username': username,
                'row': row,
            }

            domain_info = get_domain_info(domain, upload_domain, user_specs,
                                          domain_info_by_domain,
                                          group_memoizer)

            try:
                for validator in domain_info.validators:
                    validator(row)
            except UserUploadError as e:
                status_row['flag'] = str(e)
                ret['rows'].append(status_row)
                continue

            data = row.get('data', {})
            email = row.get('email')
            group_names = list(map(str, row.get('group') or []))
            language = row.get('language')
            name = row.get('name')
            password = row.get('password')
            phone_number = row.get('phone-number')
            uncategorized_data = row.get('uncategorized_data', {})
            user_id = row.get('user_id')
            location_codes = row.get('location_code',
                                     []) if 'location_code' in row else None
            location_codes = format_location_codes(location_codes)
            role = row.get('role', None)
            profile = row.get('user_profile', None)
            web_user = row.get('web_user')

            try:
                password = str(password) if password else None

                is_active = spec_value_to_boolean_or_none(row, 'is_active')
                is_account_confirmed = spec_value_to_boolean_or_none(
                    row, 'is_account_confirmed')
                send_account_confirmation_email = spec_value_to_boolean_or_none(
                    row, 'send_confirmation_email')
                remove_web_user = spec_value_to_boolean_or_none(
                    row, 'remove_web_user')

                if user_id:
                    user = CommCareUser.get_by_user_id(user_id, domain)
                    if not user:
                        raise UserUploadError(
                            _("User with ID '{user_id}' not found").format(
                                user_id=user_id, domain=domain))
                    check_changing_username(user, username)

                    # note: explicitly not including "None" here because that's the default value if not set.
                    # False means it was set explicitly to that value
                    if is_account_confirmed is False and not web_user:
                        raise UserUploadError(
                            _("You can only set 'Is Account Confirmed' to 'False' on a new User."
                              ))

                    if is_password(password):
                        user.set_password(password)
                        # overwrite password in results so we do not save it to the db
                        status_row['row']['password'] = '******'
                    status_row['flag'] = 'updated'
                else:
                    kwargs = {}
                    if is_account_confirmed is not None and not web_user:
                        kwargs['is_account_confirmed'] = is_account_confirmed
                    user = CommCareUser.create(
                        domain,
                        username,
                        password,
                        created_by=upload_user,
                        created_via=USER_CHANGE_VIA_BULK_IMPORTER,
                        commit=False,
                        **kwargs)
                    log_user_create = True
                    status_row['flag'] = 'created'

                if phone_number:
                    user.add_phone_number(_fmt_phone(phone_number),
                                          default=True)
                if name:
                    user.set_full_name(str(name))

                # Add in existing data. Don't use metadata - we don't want to add profile-controlled fields.
                for key, value in user.user_data.items():
                    if key not in data:
                        data[key] = value
                if profile:
                    profile_obj = domain_info.profiles_by_name[profile]
                    data[PROFILE_SLUG] = profile_obj.id
                    for key in profile_obj.fields.keys():
                        user.pop_metadata(key)
                try:
                    user.update_metadata(data)
                except ValueError as e:
                    raise UserUploadError(str(e))
                if uncategorized_data:
                    user.update_metadata(uncategorized_data)

                # Clear blank user data so that it can be purged by remove_unused_custom_fields_from_users_task
                for key in dict(data, **uncategorized_data):
                    value = user.metadata[key]
                    if value is None or value == '':
                        user.pop_metadata(key)

                if language:
                    user.language = language
                if email:
                    user.email = email.lower()
                if is_active is not None:
                    user.is_active = is_active

                if domain_info.can_assign_locations and location_codes is not None:
                    # Do this here so that we validate the location code before we
                    # save any other information to the user, this way either all of
                    # the user's information is updated, or none of it

                    # Do not update location info if the column is not included at all
                    location_ids = find_location_id(location_codes,
                                                    domain_info.location_cache)
                    locations_updated, primary_loc_removed = check_modified_user_loc(
                        location_ids, user.location_id,
                        user.assigned_location_ids)
                    if primary_loc_removed:
                        user.unset_location(commit=False)
                    if locations_updated:
                        user.reset_locations(location_ids, commit=False)

                if role:
                    role_qualified_id = domain_info.roles_by_name[
                        role].get_qualified_id()
                    user_current_role = user.get_role(domain=domain)
                    log_role_update = not (
                        user_current_role
                        and user_current_role.get_qualified_id()
                        == role_qualified_id)
                    if log_role_update:
                        user.set_role(domain, role_qualified_id)

                if web_user:
                    user.update_metadata({'login_as_user': web_user})

                user.save()
                if log_user_create:
                    user.log_user_create(upload_user,
                                         USER_CHANGE_VIA_BULK_IMPORTER)
                if log_role_update:
                    log_user_role_update(domain, user, upload_user,
                                         USER_CHANGE_VIA_BULK_IMPORTER)
                if web_user:
                    check_can_upload_web_users(upload_user)
                    current_user = CouchUser.get_by_username(web_user)
                    if remove_web_user:
                        remove_web_user_from_domain(domain, current_user,
                                                    username, upload_user)
                    else:
                        check_user_role(username, role)
                        if not current_user and is_account_confirmed:
                            raise UserUploadError(
                                _("You can only set 'Is Account Confirmed' to 'True' on an existing Web User. {web_user} is a new username."
                                  ).format(web_user=web_user))
                        if current_user and not current_user.is_member_of(
                                domain) and is_account_confirmed:
                            current_user.add_as_web_user(
                                domain,
                                role=role_qualified_id,
                                location_id=user.location_id)

                        elif not current_user or not current_user.is_member_of(
                                domain):
                            create_or_update_web_user_invite(
                                web_user,
                                domain,
                                role_qualified_id,
                                upload_user,
                                user.location_id,
                                send_email=send_account_confirmation_email)

                        elif current_user.is_member_of(domain):
                            # edit existing user in the domain
                            current_user.set_role(domain, role_qualified_id)
                            if location_codes is not None:
                                if user.location_id:
                                    current_user.set_location(
                                        domain, user.location_id)
                                else:
                                    current_user.unset_location(domain)
                            current_user.save()

                if send_account_confirmation_email and not web_user:
                    send_account_confirmation_if_necessary(user)

                if is_password(password):
                    # Without this line, digest auth doesn't work.
                    # With this line, digest auth works.
                    # Other than that, I'm not sure what's going on
                    # Passing use_primary_db=True because of https://dimagi-dev.atlassian.net/browse/ICDS-465
                    user.get_django_user(
                        use_primary_db=True).check_password(password)

                for group in domain_info.group_memoizer.by_user_id(
                        user.user_id):
                    if group.name not in group_names:
                        group.remove_user(user)

                for group_name in group_names:
                    domain_info.group_memoizer.by_name(group_name).add_user(
                        user, save=False)

            except (UserUploadError, CouchUser.Inconsistent) as e:
                status_row['flag'] = str(e)

            ret["rows"].append(status_row)
    finally:
        try:
            for domain_info in domain_info_by_domain.values():
                domain_info.group_memoizer.save_all()
        except BulkSaveError as e:
            _error_message = (
                "Oops! We were not able to save some of your group changes. "
                "Please make sure no one else is editing your groups "
                "and try again.")
            logging.exception(('BulkSaveError saving groups. '
                               'User saw error message "%s". Errors: %s') %
                              (_error_message, e.errors))
            ret['errors'].append(_error_message)

    return ret