Skip to content

UserItem.CSVImport: several bugs prevent reliable use and block tabcmd delegation #1809

Description

@jacalata

Summary

UserItem.CSVImport was added to consolidate CSV user-import logic, but several bugs currently prevent it from being used as a reliable foundation. tabcmd duplicates the entire CSV stack independently (user_data.py) and cannot yet delegate to TSC without introducing regressions. This issue documents what needs to be fixed so that tabcmd can remove its duplicate code.


Bugs

1. Off-by-one on MAX makes the AUTH column unusable

ColumnType.AUTH = 7 and ColumnType.MAX = 7 have the same value. Both create_user_from_line and _validate_import_line_or_throw guard with len > MAX, which fires for any 8-element line — i.e. any line that includes the auth type column. The auth column defined in the spec is silently unreachable.

# user_item.py line ~446
if len(values) > UserItem.CSVImport.ColumnType.MAX:  # MAX=7, so 8-column lines always raise
    raise ValueError("Too many attributes for user import")

Fix: MAX should equal the number of columns (8), not the last index (7). Change to MAX = 8 and update bounds checks to >= MAX.


2. create_user_from_line lowercases the whole line including usernames

line = line.strip().lower()  # line ~442

This lowercases the username before assignment, which is destructive for case-sensitive authentication systems (e.g. LDAP) or mixed-case email-format usernames. Only the comparison-relevant fields (license, admin, publisher, auth) should be normalized.

Fix: lowercase per-field only for the fields that need it (license, admin, publisher, auth), not the entire line.


3. _valid_attributes auth column is missing TableauIDWithMFA

[UserItem.Auth.SAML, UserItem.Auth.OpenID, UserItem.Auth.ServerDefault],  # auth — line ~504

UserItem.Auth.TableauIDWithMFA is defined in the Auth class and is a valid value (documented in Tableau Online). It should be included in the validation list.


4. validate_file_for_import doesn't normalize case before validation

validate_file_for_import passes the raw (mixed-case) line directly to _validate_import_line_or_throw, whose _valid_attributes lists are all lowercase. So Viewer fails validation but viewer passes — but _evaluate_site_role handles both correctly. This means validate_file_for_import rejects CSVs that create_user_from_line would parse successfully.

Fix: lowercase the line (or individual non-username fields) in _validate_import_line_or_throw before comparison, matching the behaviour of create_user_from_line.


5. _set_values bypasses the @property_is_enum guard on auth_setting

create_user_from_line calls _set_values(...) which writes directly to self._auth_setting, skipping the @property_is_enum(Auth) setter. A CSV-parsed UserItem can therefore carry an invalid auth_setting that only fails at the API call, not at construction.

if auth_setting:
    self._auth_setting = auth_setting  # bypasses @property_is_enum

Fix: use self.auth_setting = auth_setting in _set_values, or validate explicitly before assignment. (Note: this requires fixing bug #3 first so TableauIDWithMFA is accepted.)


Context: tabcmd duplication

tabcmd (tabcmd/commands/user/user_data.py) contains a complete independent re-implementation of CSVImport: _parse_line, get_users_from_file, validate_file_for_import, _validate_user_or_throw, _validate_item, and evaluate_site_role. The role derivation function is copied character-for-character (including a # is this the expected outcome? comment on the Unlicensed fallback).

Once the above bugs are fixed, tabcmd can delete ~100 lines of duplicate code and delegate to UserItem.CSVImport. A follow-up tabcmd PR will do this once this issue is resolved.

Affected code

  • tableauserverclient/models/user_item.pyUserItem.CSVImport class (lines ~418–566)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions