20
20
21
21
import okta
22
22
import six
23
+ import string
23
24
from okta .framework .OktaError import OktaError
24
25
25
26
import user_sync .config
@@ -66,13 +67,31 @@ def __init__(self, caller_options):
66
67
'{group}' )
67
68
builder .set_string_value ('all_users_filter' ,
68
69
'user.status == "ACTIVE"' )
70
+ builder .set_string_value ('string_encoding' , 'utf8' )
71
+ builder .set_string_value ('user_identity_type_format' , None )
72
+ builder .set_string_value ('user_email_format' , six .text_type ('{email}' ))
73
+ builder .set_string_value ('user_username_format' , None )
74
+ builder .set_string_value ('user_domain_format' , None )
75
+ builder .set_string_value ('user_given_name_format' , six .text_type ('{firstName}' ))
76
+ builder .set_string_value ('user_surname_format' , six .text_type ('{lastName}' ))
77
+ builder .set_string_value ('user_country_code_format' , six .text_type ('{countryCode}' ))
69
78
builder .set_string_value ('user_identity_type' , None )
70
79
builder .set_string_value ('logger_name' , self .name )
71
80
host = builder .require_string_value ('host' )
72
81
api_token = builder .require_string_value ('api_token' )
73
82
74
83
options = builder .get_options ()
75
84
85
+ OKTAValueFormatter .encoding = options ['string_encoding' ]
86
+ self .user_identity_type = user_sync .identity_type .parse_identity_type (options ['user_identity_type' ])
87
+ self .user_identity_type_formatter = OKTAValueFormatter (options ['user_identity_type_format' ])
88
+ self .user_email_formatter = OKTAValueFormatter (options ['user_email_format' ])
89
+ self .user_username_formatter = OKTAValueFormatter (options ['user_username_format' ])
90
+ self .user_domain_formatter = OKTAValueFormatter (options ['user_domain_format' ])
91
+ self .user_given_name_formatter = OKTAValueFormatter (options ['user_given_name_format' ])
92
+ self .user_surname_formatter = OKTAValueFormatter (options ['user_surname_format' ])
93
+ self .user_country_code_formatter = OKTAValueFormatter (options ['user_country_code_format' ])
94
+
76
95
self .users_client = None
77
96
self .groups_client = None
78
97
self .logger = logger = user_sync .connector .helper .create_logger (options )
@@ -167,7 +186,14 @@ def iter_group_members(self, group, filter_string, extended_attributes):
167
186
:rtype iterator(str, str)
168
187
"""
169
188
170
- user_attribute_names = ["firstName" , "lastName" , "login" , "email" , "countryCode" ]
189
+ user_attribute_names = []
190
+ user_attribute_names .extend (self .user_given_name_formatter .get_attribute_names ())
191
+ user_attribute_names .extend (self .user_surname_formatter .get_attribute_names ())
192
+ user_attribute_names .extend (self .user_country_code_formatter .get_attribute_names ())
193
+ user_attribute_names .extend (self .user_identity_type_formatter .get_attribute_names ())
194
+ user_attribute_names .extend (self .user_email_formatter .get_attribute_names ())
195
+ user_attribute_names .extend (self .user_username_formatter .get_attribute_names ())
196
+ user_attribute_names .extend (self .user_domain_formatter .get_attribute_names ())
171
197
extended_attributes = list (set (extended_attributes ) - set (user_attribute_names ))
172
198
user_attribute_names .extend (extended_attributes )
173
199
@@ -181,11 +207,6 @@ def iter_group_members(self, group, filter_string, extended_attributes):
181
207
raise AssertionException ("Okta error querying for group users: %s" % e )
182
208
# Filtering users based all_users_filter query in config
183
209
for member in self .filter_users (members , filter_string ):
184
- profile = member .profile
185
- if not profile .email :
186
- self .logger .warning ('No email attribute for login: %s' , profile .login )
187
- continue
188
-
189
210
user = self .convert_user (member , extended_attributes )
190
211
if not user :
191
212
continue
@@ -194,13 +215,19 @@ def iter_group_members(self, group, filter_string, extended_attributes):
194
215
self .logger .warning ("No group found for: %s" , group )
195
216
196
217
def convert_user (self , record , extended_attributes ):
197
- profile = record .profile
198
218
199
219
source_attributes = {}
220
+ source_attributes ['login' ] = login = OKTAValueFormatter .get_profile_value (record ,'login' )
221
+ email , last_attribute_name = self .user_email_formatter .generate_value (record )
222
+ email = email .strip () if email else None
223
+ if not email :
224
+ if last_attribute_name is not None :
225
+ self .logger .warning ('Skipping user with login %s: empty email attribute (%s)' , login , last_attribute_name )
226
+ return None
200
227
user = user_sync .connector .helper .create_blank_user ()
201
-
202
228
source_attributes ['id' ] = user ['uid' ] = record .id
203
- source_attributes ['email' ] = user ['email' ] = profile .email
229
+ source_attributes ['email' ] = email
230
+ user ['email' ] = email
204
231
205
232
source_attributes ['identity_type' ] = user_identity_type = self .user_identity_type
206
233
if not user_identity_type :
@@ -209,37 +236,55 @@ def convert_user(self, record, extended_attributes):
209
236
try :
210
237
user ['identity_type' ] = user_sync .identity_type .parse_identity_type (user_identity_type )
211
238
except AssertionException as e :
212
- self .logger .warning ('Skipping user %s: %s' , profile . login , e )
239
+ self .logger .warning ('Skipping user %s: %s' , login , e )
213
240
return None
214
241
215
- source_attributes ['login' ] = profile .login
216
-
217
- user ['username' ] = ''
218
242
219
- if profile .firstName :
220
- source_attributes ['firstName' ] = user ['firstname' ] = profile .firstName
221
- else :
222
- source_attributes ['firstName' ] = None
223
-
224
- if profile .lastName :
225
- source_attributes ['lastName' ] = user ['lastname' ] = profile .lastName
226
- else :
227
- source_attributes ['lastName' ] = None
228
243
229
- if profile .countryCode :
230
- source_attributes ['countryCode' ] = profile .countryCode
231
- user ['country' ] = profile .countryCode .upper ()
244
+ username , last_attribute_name = self .user_username_formatter .generate_value (record )
245
+ username = username .strip () if username else None
246
+ source_attributes ['username' ] = username
247
+ if username :
248
+ user ['username' ] = username
232
249
else :
233
- source_attributes ['countryCode' ] = None
234
-
235
- if extended_attributes :
250
+ if last_attribute_name :
251
+ self .logger .warning ('No username attribute (%s) for user with login: %s, default to email (%s)' ,
252
+ last_attribute_name , login , email )
253
+ user ['username' ] = email
254
+
255
+ domain , last_attribute_name = self .user_domain_formatter .generate_value (record )
256
+ domain = domain .strip () if domain else None
257
+ source_attributes ['domain' ] = domain
258
+ if domain :
259
+ user ['domain' ] = domain
260
+ elif username != email :
261
+ user ['domain' ] = email [email .find ('@' ) + 1 :]
262
+ elif last_attribute_name :
263
+ self .logger .warning ('No domain attribute (%s) for user with login: %s' , last_attribute_name , login )
264
+
265
+ first_name_value , last_attribute_name = self .user_given_name_formatter .generate_value (record )
266
+ source_attributes ['firstName' ] = first_name_value
267
+ if first_name_value is not None :
268
+ user ['firstname' ] = first_name_value
269
+ elif last_attribute_name :
270
+ self .logger .warning ('No given name attribute (%s) for user with login: %s' , last_attribute_name , login )
271
+ last_name_value , last_attribute_name = self .user_surname_formatter .generate_value (record )
272
+ source_attributes ['lastName' ] = last_name_value
273
+ if last_name_value is not None :
274
+ user ['lastname' ] = last_name_value
275
+ elif last_attribute_name :
276
+ self .logger .warning ('No last name attribute (%s) for user with login: %s' , last_attribute_name , login )
277
+ country_value , last_attribute_name = self .user_country_code_formatter .generate_value (record )
278
+ source_attributes ['c' ] = country_value
279
+ if country_value is not None :
280
+ user ['country' ] = country_value .upper ()
281
+ elif last_attribute_name :
282
+ self .logger .warning ('No country code attribute (%s) for user with login: %s' , last_attribute_name , login )
283
+
284
+ if extended_attributes is not None :
236
285
for extended_attribute in extended_attributes :
237
- if extended_attribute not in source_attributes :
238
- if hasattr (profile , extended_attribute ):
239
- extended_attribute_value = getattr (profile , extended_attribute )
240
- source_attributes [extended_attribute ] = extended_attribute_value
241
- else :
242
- source_attributes [extended_attribute ] = None
286
+ extended_attribute_value = OKTAValueFormatter .get_profile_value (record , extended_attribute )
287
+ source_attributes [extended_attribute ] = extended_attribute_value
243
288
244
289
user ['source_attributes' ] = source_attributes .copy ()
245
290
return user
@@ -273,6 +318,27 @@ def filter_users(self, users, filter_string):
273
318
274
319
275
320
class OKTAValueFormatter (object ):
321
+ encoding = 'utf8'
322
+
323
+ def __init__ (self , string_format ):
324
+ """
325
+ The format string must be a unicode or ascii string: see notes above about being careful in Py2!
326
+ """
327
+ if string_format is None :
328
+ attribute_names = []
329
+ else :
330
+ string_format = six .text_type (string_format ) # force unicode so attribute values are unicode
331
+ formatter = string .Formatter ()
332
+ attribute_names = [six .text_type (item [1 ]) for item in formatter .parse (string_format ) if item [1 ]]
333
+ self .string_format = string_format
334
+ self .attribute_names = attribute_names
335
+
336
+ def get_attribute_names (self ):
337
+ """
338
+ :rtype list(str)
339
+ """
340
+ return self .attribute_names
341
+
276
342
@staticmethod
277
343
def get_extended_attribute_dict (attributes ):
278
344
@@ -282,3 +348,38 @@ def get_extended_attribute_dict(attributes):
282
348
attr_dict .update ({attribute : str })
283
349
284
350
return attr_dict
351
+
352
+ def generate_value (self , record ):
353
+ """
354
+ :type record: dict
355
+ :rtype (unicode, unicode)
356
+ """
357
+ result = None
358
+ attribute_name = None
359
+ if self .string_format is not None :
360
+ values = {}
361
+ for attribute_name in self .attribute_names :
362
+ value = self .get_profile_value (record , attribute_name )
363
+ if value is None :
364
+ values = None
365
+ break
366
+ values [attribute_name ] = value
367
+ if values is not None :
368
+ result = self .string_format .format (** values )
369
+ return result , attribute_name
370
+
371
+ @classmethod
372
+ def get_profile_value (cls , record , attribute_name ):
373
+ """
374
+ The attribute value type must be decodable (str in py2, bytes in py3)
375
+ :type record: okta.models.user.User
376
+ :type attribute_name: unicode
377
+ """
378
+ if hasattr (record .profile , attribute_name ):
379
+ attribute_values = getattr (record .profile ,attribute_name )
380
+ if attribute_values :
381
+ try :
382
+ return attribute_values .decode (cls .encoding )
383
+ except UnicodeError as e :
384
+ raise AssertionException ("Encoding error in value of attribute '%s': %s" % (attribute_name , e ))
385
+ return None
0 commit comments