Beispiel #1
0
def create_or_update_users_and_groups(domain, user_specs, group_specs, location_specs, task=None):
    from corehq.apps.users.views.mobile.custom_data_fields import UserFieldsView
    custom_data_validator = UserFieldsView.get_validator(domain)
    ret = {"errors": [], "rows": []}
    total = len(user_specs) + len(group_specs) + len(location_specs)

    def _set_progress(progress):
        if task is not None:
            DownloadBase.set_progress(task, progress, total)

    group_memoizer = create_or_update_groups(domain, group_specs, log=ret)
    current = len(group_specs)

    usernames = set()
    user_ids = set()
    allowed_groups = set(group_memoizer.groups)
    allowed_group_names = [group.name for group in allowed_groups]
    can_access_locations = domain_has_privilege(domain, privileges.LOCATIONS)
    if can_access_locations:
        location_cache = SiteCodeToLocationCache(domain)
    try:
        for row in user_specs:
            _set_progress(current)
            current += 1

            data = row.get('data')
            email = row.get('email')
            group_names = map(unicode, 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')
            username = row.get('username')
            location_code = row.get('location_code', '')

            if password:
                password = unicode(password)
            try:
                username = normalize_username(str(username), domain)
            except TypeError:
                username = None
            except ValidationError:
                ret['rows'].append({
                    'username': username,
                    'row': row,
                    'flag': _('username cannot contain spaces or symbols'),
                })
                continue
            status_row = {
                'username': raw_username(username) if username else None,
                'row': row,
            }

            is_active = row.get('is_active')
            if isinstance(is_active, basestring):
                try:
                    is_active = string_to_boolean(is_active) if is_active else None
                except ValueError:
                    ret['rows'].append({
                        'username': username,
                        'row': row,
                        'flag': _("'is_active' column can only contain 'true' or 'false'"),
                    })
                    continue

            if username in usernames or user_id in user_ids:
                status_row['flag'] = 'repeat'
            elif not username and not user_id:
                status_row['flag'] = 'missing-data'
            else:
                try:
                    if username:
                        usernames.add(username)
                    if user_id:
                        user_ids.add(user_id)
                    if user_id:
                        user = CommCareUser.get_by_user_id(user_id, domain)
                    else:
                        user = CommCareUser.get_by_username(username)

                    def is_password(password):
                        if not password:
                            return False
                        for c in password:
                            if c != "*":
                                return True
                        return False

                    if user:
                        if user.domain != domain:
                            raise UserUploadError(_(
                                'User with username %(username)r is '
                                'somehow in domain %(domain)r'
                            ) % {'username': user.username, 'domain': user.domain})
                        if username and user.username != username:
                            user.change_username(username)
                        if is_password(password):
                            user.set_password(password)
                        status_row['flag'] = 'updated'
                    else:
                        max_username_length = get_mobile_worker_max_username_length(domain)
                        if len(raw_username(username)) > max_username_length:
                            ret['rows'].append({
                                'username': username,
                                'row': row,
                                'flag': _("username cannot contain greater than %d characters" %
                                          max_username_length)
                            })
                            continue
                        if not is_password(password):
                            raise UserUploadError(_("Cannot create a new user with a blank password"))
                        user = CommCareUser.create(domain, username, password, commit=False)
                        status_row['flag'] = 'created'
                    if phone_number:
                        user.add_phone_number(_fmt_phone(phone_number), default=True)
                    if name:
                        user.set_full_name(unicode(name))
                    if data:
                        error = custom_data_validator(data)
                        if error:
                            raise UserUploadError(error)
                        user.user_data.update(data)
                    if uncategorized_data:
                        user.user_data.update(uncategorized_data)
                    if language:
                        user.language = language
                    if email:
                        email_validator = EmailValidator()
                        try:
                            email_validator(email)
                            user.email = email
                        except ValidationError:
                            raise UserUploadError(_("User has an invalid email address"))
                    if is_active is not None:
                        user.is_active = is_active

                    if can_access_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
                        if location_code:
                            loc = get_location_from_site_code(location_code, location_cache)
                        else:
                            loc = None

                    user.save()
                    if can_access_locations and loc:
                        if user.location_id != loc.location_id:
                            # this triggers a second user save so
                            # we want to avoid doing it if it isn't
                            # needed
                            user.set_location(loc)

                    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
                        user.get_django_user().check_password(password)

                    for group_id in Group.by_user(user, wrap=False):
                        group = group_memoizer.get(group_id)
                        if group.name not in group_names:
                            group.remove_user(user, save=False)

                    for group_name in group_names:
                        if group_name not in allowed_group_names:
                            raise UserUploadError(_(
                                "Can't add to group '%s' "
                                "(try adding it to your spreadsheet)"
                            ) % group_name)
                        group_memoizer.by_name(group_name).add_user(user, save=False)

                except (UserUploadError, CouchUser.Inconsistent) as e:
                    status_row['flag'] = unicode(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)

    if Domain.get_by_name(domain).supports_multiple_locations_per_user:
        create_or_update_locations(domain, location_specs, log=ret)
    _set_progress(total)
    return ret
Beispiel #2
0
 def __init__(self, domain):
     super().__init__(domain)
     from corehq.apps.users.views.mobile.custom_data_fields import UserFieldsView
     self.custom_data_validator = UserFieldsView.get_validator(domain)
Beispiel #3
0
def create_or_update_users_and_groups(domain,
                                      user_specs,
                                      group_specs,
                                      task=None):
    from corehq.apps.users.views.mobile.custom_data_fields import UserFieldsView
    custom_data_validator = UserFieldsView.get_validator(domain)
    ret = {"errors": [], "rows": []}
    total = len(user_specs) + len(group_specs)

    def _set_progress(progress):
        if task is not None:
            DownloadBase.set_progress(task, progress, total)

    group_memoizer = create_or_update_groups(domain, group_specs, log=ret)
    current = len(group_specs)

    usernames = set()
    user_ids = set()
    allowed_groups = set(group_memoizer.groups)
    allowed_group_names = [group.name for group in allowed_groups]
    allowed_roles = UserRole.by_domain(domain)
    roles_by_name = {role.name: role for role in allowed_roles}
    can_assign_locations = domain_has_privilege(domain, privileges.LOCATIONS)
    if can_assign_locations:
        location_cache = SiteCodeToLocationCache(domain)
    domain_obj = Domain.get_by_name(domain)
    usernames_with_dupe_passwords = users_with_duplicate_passwords(user_specs)

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

            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')
            username = row.get('username')
            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', '')

            if password:
                password = str(password)
            try:
                username = normalize_username(str(username), domain)
            except TypeError:
                username = None
            except ValidationError:
                ret['rows'].append({
                    'username':
                    username,
                    'row':
                    row,
                    'flag':
                    _('username cannot contain spaces or symbols'),
                })
                continue
            status_row = {
                'username': raw_username(username) if username else None,
                'row': row,
            }

            is_active = row.get('is_active')
            if isinstance(is_active, str):
                try:
                    is_active = string_to_boolean(
                        is_active) if is_active else None
                except ValueError:
                    ret['rows'].append({
                        'username':
                        username,
                        'row':
                        row,
                        'flag':
                        _("'is_active' column can only contain 'true' or 'false'"
                          ),
                    })
                    continue

            if username in usernames or user_id in user_ids:
                status_row['flag'] = 'repeat'
            elif not username and not user_id:
                status_row['flag'] = 'missing-data'
            else:
                try:
                    if username:
                        usernames.add(username)
                    if user_id:
                        user_ids.add(user_id)
                    if user_id:
                        user = CommCareUser.get_by_user_id(user_id, domain)
                    else:
                        user = CommCareUser.get_by_username(username)

                    if domain_obj.strong_mobile_passwords and is_password(
                            password):
                        if raw_username(
                                username) in usernames_with_dupe_passwords:
                            raise UserUploadError(
                                _("Provide a unique password for each mobile worker"
                                  ))

                        try:
                            clean_password(password)
                        except forms.ValidationError:
                            if settings.ENABLE_DRACONIAN_SECURITY_FEATURES:
                                msg = _(
                                    "Mobile Worker passwords must be 8 "
                                    "characters long with at least 1 capital "
                                    "letter, 1 special character and 1 number")
                            else:
                                msg = _("Please provide a stronger password")
                            raise UserUploadError(msg)

                    if user:
                        if user.domain != domain:
                            raise UserUploadError(
                                _('User with username %(username)r is '
                                  'somehow in domain %(domain)r') % {
                                      'username': user.username,
                                      'domain': user.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
                                  })
                        if is_password(password):
                            user.set_password(password)
                        status_row['flag'] = 'updated'
                    else:
                        max_username_length = get_mobile_worker_max_username_length(
                            domain)
                        if len(raw_username(username)) > max_username_length:
                            ret['rows'].append({
                                'username':
                                username,
                                'row':
                                row,
                                'flag':
                                _("username cannot contain greater than %d characters"
                                  % max_username_length)
                            })
                            continue
                        if not is_password(password):
                            raise UserUploadError(
                                _("Cannot create a new user with a blank password"
                                  ))
                        user = CommCareUser.create(domain,
                                                   username,
                                                   password,
                                                   commit=False)
                        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:
                        error = custom_data_validator(data)
                        if error:
                            raise UserUploadError(error)
                        user.user_data.update(data)
                    if uncategorized_data:
                        user.user_data.update(uncategorized_data)
                    if language:
                        user.language = language
                    if email:
                        try:
                            validate_email(email)
                        except ValidationError:
                            raise UserUploadError(
                                _("User has an invalid email address"))

                        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)

                    if role:
                        if role in roles_by_name:
                            user.set_role(
                                domain, roles_by_name[role].get_qualified_id())
                        else:
                            raise UserUploadError(
                                _("Role '%s' does not exist") % role)

                    if can_assign_locations:
                        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)

                    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_id in Group.by_user_id(user.user_id, wrap=False):
                        group = group_memoizer.get(group_id)
                        if group.name not in group_names:
                            group.remove_user(user)

                    for group_name in group_names:
                        if group_name not in allowed_group_names:
                            raise UserUploadError(
                                _("Can't add to group '%s' "
                                  "(try adding it to your spreadsheet)") %
                                group_name)
                        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)

    _set_progress(total)
    return ret
Beispiel #4
0
def create_or_update_users_and_groups(domain, user_specs, group_specs, task=None):
    from corehq.apps.users.views.mobile.custom_data_fields import UserFieldsView
    custom_data_validator = UserFieldsView.get_validator(domain)
    ret = {"errors": [], "rows": []}
    total = len(user_specs) + len(group_specs)

    def _set_progress(progress):
        if task is not None:
            DownloadBase.set_progress(task, progress, total)

    group_memoizer = create_or_update_groups(domain, group_specs, log=ret)
    current = len(group_specs)

    usernames = set()
    user_ids = set()
    allowed_groups = set(group_memoizer.groups)
    allowed_group_names = [group.name for group in allowed_groups]
    allowed_roles = UserRole.by_domain(domain)
    roles_by_name = {role.name: role for role in allowed_roles}
    can_assign_locations = domain_has_privilege(domain, privileges.LOCATIONS)
    if can_assign_locations:
        location_cache = SiteCodeToLocationCache(domain)
    domain_obj = Domain.get_by_name(domain)
    usernames_with_dupe_passwords = users_with_duplicate_passwords(user_specs)

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

            data = row.get('data')
            email = row.get('email')
            group_names = list(map(six.text_type, 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')
            username = row.get('username')
            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', '')

            if password:
                password = six.text_type(password)
            try:
                username = normalize_username(six.text_type(username), domain)
            except TypeError:
                username = None
            except ValidationError:
                ret['rows'].append({
                    'username': username,
                    'row': row,
                    'flag': _('username cannot contain spaces or symbols'),
                })
                continue
            status_row = {
                'username': raw_username(username) if username else None,
                'row': row,
            }

            is_active = row.get('is_active')
            if isinstance(is_active, six.string_types):
                soft_assert_type_text(is_active)
                try:
                    is_active = string_to_boolean(is_active) if is_active else None
                except ValueError:
                    ret['rows'].append({
                        'username': username,
                        'row': row,
                        'flag': _("'is_active' column can only contain 'true' or 'false'"),
                    })
                    continue

            if username in usernames or user_id in user_ids:
                status_row['flag'] = 'repeat'
            elif not username and not user_id:
                status_row['flag'] = 'missing-data'
            else:
                try:
                    if username:
                        usernames.add(username)
                    if user_id:
                        user_ids.add(user_id)
                    if user_id:
                        user = CommCareUser.get_by_user_id(user_id, domain)
                    else:
                        user = CommCareUser.get_by_username(username)

                    if domain_obj.strong_mobile_passwords and is_password(password):
                        if raw_username(username) in usernames_with_dupe_passwords:
                            raise UserUploadError(_("Provide a unique password for each mobile worker"))

                        try:
                            clean_password(password)
                        except forms.ValidationError:
                            if settings.ENABLE_DRACONIAN_SECURITY_FEATURES:
                                msg = _("Mobile Worker passwords must be 8 "
                                    "characters long with at least 1 capital "
                                    "letter, 1 special character and 1 number")
                            else:
                                msg = _("Please provide a stronger password")
                            raise UserUploadError(msg)

                    if user:
                        if user.domain != domain:
                            raise UserUploadError(_(
                                'User with username %(username)r is '
                                'somehow in domain %(domain)r'
                            ) % {'username': user.username, 'domain': user.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})
                        if is_password(password):
                            user.set_password(password)
                        status_row['flag'] = 'updated'
                    else:
                        max_username_length = get_mobile_worker_max_username_length(domain)
                        if len(raw_username(username)) > max_username_length:
                            ret['rows'].append({
                                'username': username,
                                'row': row,
                                'flag': _("username cannot contain greater than %d characters" %
                                          max_username_length)
                            })
                            continue
                        if not is_password(password):
                            raise UserUploadError(_("Cannot create a new user with a blank password"))
                        user = CommCareUser.create(domain, username, password, commit=False)
                        status_row['flag'] = 'created'
                    if phone_number:
                        user.add_phone_number(_fmt_phone(phone_number), default=True)
                    if name:
                        user.set_full_name(six.text_type(name))
                    if data:
                        error = custom_data_validator(data)
                        if error:
                            raise UserUploadError(error)
                        user.user_data.update(data)
                    if uncategorized_data:
                        user.user_data.update(uncategorized_data)
                    if language:
                        user.language = language
                    if email:
                        try:
                            validate_email(email)
                        except ValidationError:
                            raise UserUploadError(_("User has an invalid email address"))

                        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)

                    if role:
                        if role in roles_by_name:
                            user.set_role(domain, roles_by_name[role].get_qualified_id())
                        else:
                            raise UserUploadError(_(
                                "Role '%s' does not exist"
                            ) % role)

                    if can_assign_locations:
                        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)

                    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_id in Group.by_user(user, wrap=False):
                        group = group_memoizer.get(group_id)
                        if group.name not in group_names:
                            group.remove_user(user)

                    for group_name in group_names:
                        if group_name not in allowed_group_names:
                            raise UserUploadError(_(
                                "Can't add to group '%s' "
                                "(try adding it to your spreadsheet)"
                            ) % group_name)
                        group_memoizer.by_name(group_name).add_user(user, save=False)

                except (UserUploadError, CouchUser.Inconsistent) as e:
                    status_row['flag'] = six.text_type(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)

    _set_progress(total)
    return ret