@@ -308,6 +308,14 @@ def _set_values(
308308 if email :
309309 self .email = email
310310 if auth_setting :
311+ # Write directly to _auth_setting rather than going through the
312+ # @property_is_enum(Auth) setter. This method is called from both
313+ # CSV import and server response parsing (from_xml, populate, etc.);
314+ # if the server ever returns an auth type we don't yet know about
315+ # (a new Auth value in a future Tableau release), the enum guard
316+ # would raise ValueError during response parsing. CSV callers
317+ # already validate the auth string against CSVImport._AUTH_CANONICAL
318+ # before calling here, so this write is safe.
311319 self ._auth_setting = auth_setting
312320 if domain_name :
313321 self ._domain_name = domain_name
@@ -432,36 +440,59 @@ class ColumnType(IntEnum):
432440 EMAIL = 6
433441 AUTH = 7
434442
435- MAX = 7
443+ # Total number of columns supported by the import format. Held outside
444+ # the ColumnType enum so it can't be mistaken for a real column index.
445+ COLUMN_COUNT = 8
446+
447+ # Lowercase -> canonical form mapping for the AUTH column. Class-level
448+ # so the dict isn't rebuilt on every call to create_user_from_line /
449+ # _validate_import_line_or_throw. The set of accepted values is derived
450+ # from this map (see _valid_attributes[AUTH]) so there's a single
451+ # source of truth.
452+ _AUTH_CANONICAL : dict [str , str ] = {
453+ "saml" : "SAML" ,
454+ "openid" : "OpenID" ,
455+ "serverdefault" : "ServerDefault" ,
456+ "tableauidwithmfa" : "TableauIDWithMFA" ,
457+ }
436458
437459 # Read a csv line and create a user item populated by the given attributes
438460 @staticmethod
439461 def create_user_from_line (line : str ):
440462 if line is None or line is False or line == "\n " or line == "" :
441463 return None
442- line = line .strip ().lower ()
443- values : list [str ] = list (map (str .strip , line .split ("," )))
444- user = UserItem (values [UserItem .CSVImport .ColumnType .USERNAME ])
464+ values : list [str ] = list (map (str .strip , line .strip ().split ("," )))
465+ if len (values ) > UserItem .CSVImport .COLUMN_COUNT :
466+ raise ValueError ("Too many attributes for user import" )
467+ username = values [UserItem .CSVImport .ColumnType .USERNAME ]
468+ user = UserItem (username )
445469 if len (values ) > 1 :
446- if len (values ) > UserItem .CSVImport .ColumnType .MAX :
447- raise ValueError ("Too many attributes for user import" )
448- while len (values ) <= UserItem .CSVImport .ColumnType .MAX :
470+ while len (values ) < UserItem .CSVImport .COLUMN_COUNT :
449471 values .append ("" )
450472 site_role = UserItem .CSVImport ._evaluate_site_role (
451473 values [UserItem .CSVImport .ColumnType .LICENSE ],
452474 values [UserItem .CSVImport .ColumnType .ADMIN ],
453475 values [UserItem .CSVImport .ColumnType .PUBLISHER ],
454476 )
455-
477+ raw_auth = values [UserItem .CSVImport .ColumnType .AUTH ]
478+ if raw_auth :
479+ auth = UserItem .CSVImport ._AUTH_CANONICAL .get (raw_auth .lower ())
480+ if auth is None :
481+ raise ValueError (
482+ f"Unknown auth setting: { raw_auth !r} . "
483+ f"Valid values: { sorted (UserItem .CSVImport ._AUTH_CANONICAL .values ())} "
484+ )
485+ else :
486+ auth = None
456487 user ._set_values (
457488 None ,
458- values [ UserItem . CSVImport . ColumnType . USERNAME ] ,
489+ username ,
459490 site_role ,
460491 None ,
461492 None ,
462493 values [UserItem .CSVImport .ColumnType .DISPLAY_NAME ],
463494 values [UserItem .CSVImport .ColumnType .EMAIL ],
464- values [ UserItem . CSVImport . ColumnType . AUTH ] ,
495+ auth ,
465496 None ,
466497 None ,
467498 None ,
@@ -493,6 +524,8 @@ def validate_file_for_import(csv_file: io.TextIOWrapper, logger) -> tuple[int, l
493524 # Iterate through each field and validate the given value against hardcoded constraints
494525 @staticmethod
495526 def _validate_import_line_or_throw (incoming , logger ) -> None :
527+ # AUTH column's valid set is derived from _AUTH_CANONICAL so there's
528+ # one source of truth for the accepted values.
496529 _valid_attributes : list [list [str ]] = [
497530 [],
498531 [],
@@ -501,20 +534,26 @@ def _validate_import_line_or_throw(incoming, logger) -> None:
501534 ["system" , "site" , "none" , "no" ], # admin
502535 ["yes" , "true" , "1" , "no" , "false" , "0" ], # publisher
503536 [],
504- [ UserItem .Auth . SAML , UserItem . Auth . OpenID , UserItem . Auth . ServerDefault ], # auth
537+ list ( UserItem .CSVImport . _AUTH_CANONICAL . values ()), # auth - normalized before comparison
505538 ]
506539
507540 line = list (map (str .strip , incoming .split ("," )))
508- if len (line ) > UserItem .CSVImport .ColumnType . MAX :
509- raise AttributeError ("Too many attributes in line " )
541+ if len (line ) > UserItem .CSVImport .COLUMN_COUNT :
542+ raise ValueError ("Too many attributes for user import " )
510543 username = line [UserItem .CSVImport .ColumnType .USERNAME .value ]
511544 logger .debug (f"> details - { username } " )
512545 UserItem .validate_username_or_throw (username )
513546 for i in range (1 , len (line )):
514- logger .debug (f"column { UserItem .CSVImport .ColumnType (i ).name } : { line [i ]} " )
515- UserItem .CSVImport ._validate_attribute_value (
516- line [i ], _valid_attributes [i ], UserItem .CSVImport .ColumnType (i )
517- )
547+ value = line [i ]
548+ valid = _valid_attributes [i ]
549+ # normalize case for fields with a restricted value set
550+ if valid :
551+ if i == UserItem .CSVImport .ColumnType .AUTH :
552+ value = UserItem .CSVImport ._AUTH_CANONICAL .get (value .lower (), value )
553+ else :
554+ value = value .lower ()
555+ logger .debug (f"column { UserItem .CSVImport .ColumnType (i ).name } : { value } " )
556+ UserItem .CSVImport ._validate_attribute_value (value , valid , UserItem .CSVImport .ColumnType (i ))
518557
519558 # Given a restricted set of possible values, confirm the item is in that set
520559 @staticmethod
@@ -524,7 +563,7 @@ def _validate_attribute_value(item: str, possible_values: list[str], column_type
524563 return
525564 if item in possible_values or possible_values == []:
526565 return
527- raise AttributeError (f"Invalid value { item } for { column_type } " )
566+ raise ValueError (f"Invalid value { item } for { column_type } " )
528567
529568 # https://help.tableau.com/current/server/en-us/csvguidelines.htm#settings_and_site_roles
530569 # This logic is hardcoded to match the existing rules for import csv files
0 commit comments