2121import logging
2222import os
2323import re
24-
2524import types
25+
26+ import keyring
2627import yaml
2728
2829import user_sync .identity_type
2930import user_sync .rules
30- from user_sync import credential_manager
3131from user_sync .error import AssertionException
3232
3333DEFAULT_MAIN_CONFIG_FILENAME = 'user-sync-config.yml'
@@ -139,20 +139,10 @@ def get_directory_connector_options(self, connector_name):
139139 '''
140140 options = {}
141141 connectors_config = self .get_directory_connector_configs ()
142- if ( connectors_config != None ) :
142+ if connectors_config is not None :
143143 connector_item = connectors_config .get_list (connector_name , True )
144144 options = self .get_dict_from_sources (connector_item )
145-
146145 options = self .combine_dicts ([options , self .options ['directory_connector_overridden_options' ]])
147- # credentials are None, a dict, or a config filename to read to get a dict
148- credentials = credential_manager .get_credentials (credential_manager .DIRECTORY_CREDENTIAL_TYPE ,
149- connector_name ,
150- config = options ,
151- config_loader = self )
152- if isinstance (credentials , types .StringTypes ):
153- credentials = ConfigFileLoader .load_other_config (credentials )
154- if isinstance (credentials , dict ):
155- options = self .combine_dicts ([options , credentials ])
156146 return options
157147
158148 def get_directory_groups (self ):
@@ -164,9 +154,9 @@ def get_directory_groups(self):
164154 raise AssertionException ("Your main configuration file is still in v1 format. Please convert it to v2." )
165155 groups_config = None
166156 directory_config = self .main_config .get_dict_config ('directory_users' , True )
167- if ( directory_config != None ) :
157+ if directory_config is not None :
168158 groups_config = directory_config .get_list_config ('groups' , True )
169- if ( groups_config == None ) :
159+ if groups_config is None :
170160 return adobe_groups_by_directory_group
171161
172162 for item in groups_config .iter_dict_configs ():
@@ -215,7 +205,6 @@ def get_dict_from_sources(self, sources):
215205 Given a list of config file paths, return the dictionary composed of all the contents
216206 of those config files, or None if the list is empty
217207 :param sources: a list of strings
218- :param owner: a string to use in error messages if we can't find a config file.
219208 :rtype dict
220209 '''
221210 if not sources :
@@ -249,10 +238,10 @@ def combine_dicts(dicts):
249238 '''
250239 result = {}
251240 for dict_item in dicts :
252- if ( isinstance (dict_item , dict ) ):
241+ if isinstance (dict_item , dict ):
253242 for dict_key , dict_item in dict_item .iteritems ():
254243 result_item = result .get (dict_key )
255- if ( isinstance (result_item , dict ) and isinstance (dict_item , dict ) ):
244+ if isinstance (result_item , dict ) and isinstance (dict_item , dict ):
256245 result_item .update (dict_item )
257246 else :
258247 result [dict_key ] = dict_item
@@ -357,26 +346,12 @@ def get_rule_options(self):
357346 def create_umapi_options (self , connector_config_sources ):
358347 options = self .get_dict_from_sources (connector_config_sources )
359348 options ['test_mode' ] = self .options ['test_mode' ]
360- enterprise_section = options .get ('enterprise' )
361- if isinstance (enterprise_section , dict ):
362- org_id = enterprise_section .get ('org_id' )
363- if (org_id != None ):
364- # credentials are None, a dict, or a config filename to read to get a dict
365- credentials = credential_manager .get_credentials (credential_manager .UMAPI_CREDENTIAL_TYPE ,
366- org_id ,
367- config = enterprise_section ,
368- config_loader = self )
369- if isinstance (credentials , types .StringTypes ):
370- credentials = ConfigFileLoader .load_other_config (credentials )
371- if isinstance (credentials , dict ):
372- options ['enterprise' ] = self .combine_dicts ([enterprise_section , credentials ])
373349 return options
374350
375351 def check_unused_config_keys (self ):
376352 directory_connectors_config = self .get_directory_connector_configs ()
377353 self .main_config .report_unused_values (self .logger , [directory_connectors_config ])
378354
379-
380355class ObjectConfig (object ):
381356 def __init__ (self , scope ):
382357 '''
@@ -420,30 +395,31 @@ def create_assertion_error(self, message):
420395 return AssertionException ("%s in: %s" % (message , self .get_full_scope ()))
421396
422397 def describe_types (self , types_to_describe ):
423- if ( types_to_describe == types .StringTypes ) :
398+ if types_to_describe == types .StringTypes :
424399 result = self .describe_types (types .StringType )
425- elif ( isinstance (types_to_describe , tuple ) ):
400+ elif isinstance (types_to_describe , tuple ):
426401 result = []
427402 for type_to_describe in types_to_describe :
428403 result .extend (self .describe_types (type_to_describe ))
429404 else :
430405 result = [types_to_describe .__name__ ]
431406 return result
432407
433- def report_unused_values (self , logger , optional_configs = []):
408+ def report_unused_values (self , logger , optional_configs = None ):
409+ optional_configs = [] if optional_configs is None else optional_configs
434410 has_error = False
435411 for config in self .iter_configs ():
436412 messages = config .describe_unused_values ()
437- if ( len (messages ) > 0 ) :
438- if ( config in optional_configs ) :
413+ if len (messages ) > 0 :
414+ if config in optional_configs :
439415 log_level = logging .WARNING
440416 else :
441417 log_level = logging .ERROR
442418 has_error = True
443419 for message in messages :
444420 logger .log (log_level , message )
445421
446- if ( has_error ) :
422+ if has_error :
447423 raise AssertionException ('Detected unused keys that are not ignorable.' )
448424
449425 def describe_unused_values (self ):
@@ -465,7 +441,7 @@ def iter_values(self, allowed_types):
465441 '''
466442 index = 0
467443 for item in self .value :
468- if ( not isinstance (item , allowed_types ) ):
444+ if not isinstance (item , allowed_types ):
469445 reported_types = self .describe_types (allowed_types )
470446 raise self .create_assertion_error ("Value should be one of these types: %s for index: %s" % (reported_types , index ))
471447 index += 1
@@ -475,7 +451,7 @@ def iter_dict_configs(self):
475451 index = 0
476452 for value in self .iter_values (dict ):
477453 config = self .find_child_config (index )
478- if ( config == None ) :
454+ if config is None :
479455 config = DictConfig ("[%s]" % index , value )
480456 self .add_child (config )
481457 yield config
@@ -500,17 +476,17 @@ def iter_keys(self):
500476
501477 def iter_unused_keys (self ):
502478 for key in self .iter_keys ():
503- if ( key not in self .accessed_keys ) :
479+ if key not in self .accessed_keys :
504480 yield key
505481
506482 def get_dict_config (self , key , none_allowed = False ):
507483 '''
508484 :rtype DictConfig
509485 '''
510486 result = self .find_child_config (key )
511- if ( result == None ) :
487+ if result is None :
512488 value = self .get_dict (key , none_allowed )
513- if ( value != None ) :
489+ if value is not None :
514490 result = DictConfig (key , value )
515491 self .add_child (result )
516492 return result
@@ -530,7 +506,7 @@ def get_bool(self, key, none_allowed = False):
530506
531507 def get_list (self , key , none_allowed = False ):
532508 value = self .get_value (key , None , none_allowed )
533- if ( value != None and not isinstance (value , list ) ):
509+ if value is not None and not isinstance (value , list ):
534510 value = [value ]
535511 return value
536512
@@ -539,9 +515,9 @@ def get_list_config(self, key, none_allowed = False):
539515 :rtype ListConfig
540516 '''
541517 result = self .find_child_config (key )
542- if ( result == None ) :
518+ if result is None :
543519 value = self .get_list (key , none_allowed )
544- if ( value != None ) :
520+ if value is not None :
545521 result = ListConfig (key , value )
546522 self .add_child (result )
547523 return result
@@ -554,21 +530,77 @@ def get_value(self, key, allowed_types, none_allowed = False):
554530 '''
555531 self .accessed_keys .add (key )
556532 result = self .value .get (key )
557- if ( result == None ) :
558- if ( not none_allowed ) :
533+ if result is None :
534+ if not none_allowed :
559535 raise self .create_assertion_error ("Value not found for key: %s" % key )
560- elif ( allowed_types != None and not isinstance (result , allowed_types ) ):
536+ elif allowed_types is not None and not isinstance (result , allowed_types ):
561537 reported_types = self .describe_types (allowed_types )
562538 raise self .create_assertion_error ("Value should be one of these types: %s for key: %s" % (reported_types , key ))
563539 return result
564540
565541 def describe_unused_values (self ):
566542 messages = []
567543 unused_keys = list (self .iter_unused_keys ())
568- if ( len (unused_keys ) > 0 ) :
544+ if len (unused_keys ) > 0 :
569545 messages .append ("Found unused keys: %s in: %s" % (unused_keys , self .get_full_scope ()))
570546 return messages
571-
547+
548+ keyring_prefix = 'secure_'
549+ keyring_suffix = '_key'
550+
551+ def has_credential (self , name ):
552+ '''
553+ Check if there is a credential setting with the given name
554+ :param name: plaintext setting name for the credential
555+ :return: setting that was specified, or None if none was
556+ '''
557+ scope = self .get_full_scope ()
558+ keyring_name = self .keyring_prefix + name + self .keyring_suffix
559+ plaintext = self .get_string (name , True )
560+ secure = self .get_string (keyring_name , True )
561+ if plaintext and secure :
562+ raise AssertionException ('%s: cannot contain setting for both "%s" and "%s"' % (scope , name , keyring_name ))
563+ if plaintext is not None :
564+ return name
565+ elif secure is not None :
566+ return keyring_name
567+ else :
568+ return None
569+
570+ def get_credential (self , name , user_name , none_allowed = False ):
571+ '''
572+ Get the credential with the given name. Raises an AssertionException if there
573+ is no credential, or if the credential is specified both in plaintext and the keyring.
574+ If the credential is kept in the keyring, the value of the keyring_name setting
575+ gives the secure storage key, and we fetch that key for the given user.
576+ :param name: setting name for the plaintext credential
577+ :param user_name: the user for whom we should fetch the service name password in secure storage
578+ :param none_allowed: whether the credential can be missing or empty
579+ :return: credential string
580+ '''
581+ keyring_name = self .keyring_prefix + name + self .keyring_suffix
582+ scope = self .get_full_scope ()
583+ # sometimes the credential is in plain text
584+ cleartext_value = self .get_string (name , True )
585+ # sometimes the value is in the keyring
586+ secure_value_key = self .get_string (keyring_name , True )
587+ # but it has to be in exactly one of those two places!
588+ if not cleartext_value and not secure_value_key and not none_allowed :
589+ raise AssertionException ('%s: must contain setting for "%s" or "%s"' % (scope , name , keyring_name ))
590+ if cleartext_value and secure_value_key :
591+ raise AssertionException ('%s: cannot contain setting for both "%s" and "%s"' % (scope , name , keyring_name ))
592+ if secure_value_key :
593+ try :
594+ value = keyring .get_password (service_name = secure_value_key , username = user_name )
595+ except Exception as e :
596+ raise AssertionException ('%s: Error accessing secure storage: %s' % (scope , e ))
597+ else :
598+ value = cleartext_value
599+ if not value and not none_allowed :
600+ raise AssertionException (
601+ '%s: No value in secure storage for user "%s", key "%s"' % (scope , user_name , secure_value_key ))
602+ return value
603+
572604class ConfigFileLoader :
573605 '''
574606 Loads config files and does pathname expansion on settings that refer to files or directories
@@ -717,8 +749,9 @@ def process_path_value(cls, val, must_exist, can_have_subdict):
717749 does the relative path processing for a value from the dictionary,
718750 which can be a string, a list of strings, or a list of strings
719751 and "tagged" strings (sub-dictionaries whose values are strings)
720- :param key: the key whose value we are processing, for error messages
721752 :param val: the value we are processing, for error messages
753+ :param must_exist: whether there must be a value
754+ :param can_have_subdict: whether the value can be a tagged string
722755 '''
723756 if isinstance (val , types .StringTypes ):
724757 return cls .relative_path (val , must_exist )
@@ -793,7 +826,7 @@ def set_value(self, key, allowed_types, default_value):
793826 '''
794827 value = default_value
795828 config = self .default_config
796- if ( config != None and config .has_key (key )):
829+ if config is not None and config .has_key (key ):
797830 value = config .get_value (key , allowed_types , False )
798831 self .options [key ] = value
799832
@@ -809,7 +842,7 @@ def require_value(self, key, allowed_types):
809842 :type key: str
810843 '''
811844 config = self .default_config
812- if ( config == None ) :
845+ if config is None :
813846 raise AssertionException ("No config found." )
814847 self .options [key ] = value = config .get_value (key , allowed_types )
815848 return value
0 commit comments