boto3-assist 0.32.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. boto3_assist/__init__.py +0 -0
  2. boto3_assist/aws_config.py +199 -0
  3. boto3_assist/aws_lambda/event_info.py +414 -0
  4. boto3_assist/aws_lambda/mock_context.py +5 -0
  5. boto3_assist/boto3session.py +87 -0
  6. boto3_assist/cloudwatch/cloudwatch_connection.py +84 -0
  7. boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +17 -0
  8. boto3_assist/cloudwatch/cloudwatch_log_connection.py +62 -0
  9. boto3_assist/cloudwatch/cloudwatch_logs.py +39 -0
  10. boto3_assist/cloudwatch/cloudwatch_query.py +191 -0
  11. boto3_assist/cognito/cognito_authorizer.py +169 -0
  12. boto3_assist/cognito/cognito_connection.py +59 -0
  13. boto3_assist/cognito/cognito_utility.py +514 -0
  14. boto3_assist/cognito/jwks_cache.py +21 -0
  15. boto3_assist/cognito/user.py +27 -0
  16. boto3_assist/connection.py +146 -0
  17. boto3_assist/connection_tracker.py +120 -0
  18. boto3_assist/dynamodb/dynamodb.py +1206 -0
  19. boto3_assist/dynamodb/dynamodb_connection.py +113 -0
  20. boto3_assist/dynamodb/dynamodb_helpers.py +333 -0
  21. boto3_assist/dynamodb/dynamodb_importer.py +102 -0
  22. boto3_assist/dynamodb/dynamodb_index.py +507 -0
  23. boto3_assist/dynamodb/dynamodb_iservice.py +29 -0
  24. boto3_assist/dynamodb/dynamodb_key.py +130 -0
  25. boto3_assist/dynamodb/dynamodb_model_base.py +382 -0
  26. boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +34 -0
  27. boto3_assist/dynamodb/dynamodb_re_indexer.py +165 -0
  28. boto3_assist/dynamodb/dynamodb_reindexer.py +165 -0
  29. boto3_assist/dynamodb/dynamodb_reserved_words.py +52 -0
  30. boto3_assist/dynamodb/dynamodb_reserved_words.txt +573 -0
  31. boto3_assist/dynamodb/readme.md +68 -0
  32. boto3_assist/dynamodb/troubleshooting.md +7 -0
  33. boto3_assist/ec2/ec2_connection.py +57 -0
  34. boto3_assist/environment_services/__init__.py +0 -0
  35. boto3_assist/environment_services/environment_loader.py +128 -0
  36. boto3_assist/environment_services/environment_variables.py +219 -0
  37. boto3_assist/erc/__init__.py +64 -0
  38. boto3_assist/erc/ecr_connection.py +57 -0
  39. boto3_assist/errors/custom_exceptions.py +46 -0
  40. boto3_assist/http_status_codes.py +80 -0
  41. boto3_assist/models/serializable_model.py +9 -0
  42. boto3_assist/role_assumption_mixin.py +38 -0
  43. boto3_assist/s3/s3.py +64 -0
  44. boto3_assist/s3/s3_bucket.py +67 -0
  45. boto3_assist/s3/s3_connection.py +76 -0
  46. boto3_assist/s3/s3_event_data.py +168 -0
  47. boto3_assist/s3/s3_object.py +695 -0
  48. boto3_assist/securityhub/securityhub.py +150 -0
  49. boto3_assist/securityhub/securityhub_connection.py +57 -0
  50. boto3_assist/session_setup_mixin.py +70 -0
  51. boto3_assist/ssm/connection.py +57 -0
  52. boto3_assist/ssm/parameter_store/parameter_store.py +116 -0
  53. boto3_assist/utilities/datetime_utility.py +349 -0
  54. boto3_assist/utilities/decimal_conversion_utility.py +140 -0
  55. boto3_assist/utilities/dictionary_utility.py +32 -0
  56. boto3_assist/utilities/file_operations.py +135 -0
  57. boto3_assist/utilities/http_utility.py +48 -0
  58. boto3_assist/utilities/logging_utility.py +0 -0
  59. boto3_assist/utilities/numbers_utility.py +329 -0
  60. boto3_assist/utilities/serialization_utility.py +664 -0
  61. boto3_assist/utilities/string_utility.py +337 -0
  62. boto3_assist/version.py +1 -0
  63. boto3_assist-0.32.0.dist-info/METADATA +76 -0
  64. boto3_assist-0.32.0.dist-info/RECORD +67 -0
  65. boto3_assist-0.32.0.dist-info/WHEEL +4 -0
  66. boto3_assist-0.32.0.dist-info/licenses/LICENSE-EXPLAINED.txt +11 -0
  67. boto3_assist-0.32.0.dist-info/licenses/LICENSE.txt +21 -0
@@ -0,0 +1,59 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ from typing import Optional
8
+ from typing import TYPE_CHECKING
9
+
10
+ from aws_lambda_powertools import Logger
11
+ from boto3_assist.connection import Connection
12
+
13
+ if TYPE_CHECKING:
14
+ from mypy_boto3_cognito_idp import CognitoIdentityProviderClient
15
+ else:
16
+ CognitoIdentityProviderClient = object
17
+
18
+
19
+ logger = Logger()
20
+
21
+
22
+ class CognitoConnection(Connection):
23
+ """Connection"""
24
+
25
+ def __init__(
26
+ self,
27
+ *,
28
+ aws_profile: Optional[str] = None,
29
+ aws_region: Optional[str] = None,
30
+ aws_access_key_id: Optional[str] = None,
31
+ aws_secret_access_key: Optional[str] = None,
32
+ ) -> None:
33
+ super().__init__(
34
+ service_name="cognito-idp",
35
+ aws_profile=aws_profile,
36
+ aws_region=aws_region,
37
+ aws_access_key_id=aws_access_key_id,
38
+ aws_secret_access_key=aws_secret_access_key,
39
+ )
40
+
41
+ self.__client: CognitoIdentityProviderClient | None = None
42
+
43
+ self.raise_on_error: bool = True
44
+
45
+ @property
46
+ def client(self) -> CognitoIdentityProviderClient:
47
+ """Client Connection"""
48
+ if self.__client is None:
49
+ logger.info("Creating Client")
50
+ self.__client = self.session.client
51
+
52
+ if self.raise_on_error and self.__client is None:
53
+ raise RuntimeError("Client is not available")
54
+ return self.__client
55
+
56
+ @client.setter
57
+ def client(self, value: CognitoIdentityProviderClient):
58
+ logger.info("Setting Client")
59
+ self.__client = value
@@ -0,0 +1,514 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ import time
8
+ from typing import List, Dict, Any, Optional, Literal
9
+
10
+
11
+ from aws_lambda_powertools import Logger
12
+
13
+ from boto3_assist.cognito.user import CognitoUser
14
+ from boto3_assist.utilities.string_utility import StringUtility
15
+ from boto3_assist.utilities.dictionary_utility import DictionaryUtilitiy
16
+ from boto3_assist.cognito.cognito_connection import CognitoConnection
17
+
18
+ logger = Logger()
19
+
20
+
21
+ class CognitoCustomAttributes:
22
+ """
23
+ Defines the custom Cognito attributes available in the application.
24
+ Use the defaults or override as needed.
25
+
26
+ Attributes:
27
+ USER_ID_KEY_NAME (str): The key for the custom user ID attribute.
28
+ TENANT_ID_KEY_NAME (str): The key for the custom tenant ID attribute.
29
+ USER_ROLES_KEY_NAME (str): The key for the custom roles attribute.
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ user_id_key: str = "custom:user-id",
35
+ tenant_id_key: str = "custom:tenant-id",
36
+ user_roles_key: str = "custom:roles",
37
+ ):
38
+ self.user_id_custom_attribute = user_id_key
39
+ self.tenant_id_custom_attribute = tenant_id_key
40
+ self.user_roles_custom_attribute = user_roles_key
41
+
42
+
43
+ class CognitoUtility(CognitoConnection):
44
+ """
45
+ A utility class for managing AWS Cognito operations, including user creation, modification, and authentication.
46
+
47
+ Inherits:
48
+ CognitoConnection: Base class providing a connection to AWS Cognito.
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ *,
54
+ aws_profile: Optional[str] = None,
55
+ aws_region: Optional[str] = None,
56
+ aws_access_key_id: Optional[str] = None,
57
+ aws_secret_access_key: Optional[str] = None,
58
+ custom_attributes: Optional[CognitoCustomAttributes] = None,
59
+ auto_lower_case_email_addresses: bool = True,
60
+ ) -> None:
61
+ super().__init__(
62
+ aws_profile=aws_profile,
63
+ aws_region=aws_region,
64
+ aws_access_key_id=aws_access_key_id,
65
+ aws_secret_access_key=aws_secret_access_key,
66
+ )
67
+
68
+ self.custom_attributes = custom_attributes
69
+ self.auto_lower_case_email_addresses = auto_lower_case_email_addresses
70
+
71
+ def admin_create_user(
72
+ self,
73
+ user_pool_id: Optional[str] = None,
74
+ temp_password: Optional[str] = None,
75
+ *,
76
+ user: CognitoUser,
77
+ send_invitation: bool = False,
78
+ retry_count: int = 0,
79
+ ) -> dict:
80
+ """
81
+ Creates a new user in Cognito with custom attributes and optional invitation handling.
82
+
83
+ Args:
84
+ user_pool_id (Optional[str]): Cognito user pool ID.
85
+ temp_password (Optional[str]): Temporary password for the user.
86
+ user (CognitoUser): The user object containing details to create the user.
87
+ send_invitation (bool): Whether to send an invitation email to the user.
88
+ retry_count (int): Number of retries for password-related issues.
89
+
90
+ Returns:
91
+ dict: Response from the AWS Cognito admin create user API.
92
+
93
+ Raises:
94
+ ValueError: If user ID or tenant ID is missing.
95
+ Exception: If user creation fails for other reasons.
96
+ """
97
+ user_supplied_password = temp_password is not None
98
+
99
+ if temp_password is None:
100
+ temp_password = StringUtility.generate_random_password(15)
101
+
102
+ if user.id is None:
103
+ raise ValueError("User id is required")
104
+
105
+ if user.tenant_id is None:
106
+ raise ValueError("Tenant id is required")
107
+
108
+ user_attributes = self.__set_user_attributes(user=user)
109
+
110
+ if not send_invitation:
111
+ user_attributes.append({"Name": "email_verified", "Value": "true"})
112
+
113
+ try:
114
+ kwargs = {
115
+ "UserPoolId": user_pool_id,
116
+ "Username": user.email,
117
+ "UserAttributes": user_attributes,
118
+ "DesiredDeliveryMediums": ["EMAIL"],
119
+ }
120
+
121
+ if not send_invitation:
122
+ kwargs["MessageAction"] = "SUPPRESS"
123
+
124
+ response = self.client.admin_create_user(**kwargs)
125
+
126
+ self.admin_set_user_password(
127
+ user_name=user.email,
128
+ password=temp_password,
129
+ user_pool_id=user_pool_id,
130
+ is_permanent=True,
131
+ )
132
+
133
+ return dict(response)
134
+
135
+ except self.client.exceptions.UsernameExistsException as e:
136
+ logger.error(f"Error: {e.response['Error']['Message']}")
137
+ raise
138
+ except self.client.exceptions.InvalidPasswordException:
139
+ if not user_supplied_password and retry_count < 5:
140
+ logger.debug(
141
+ {
142
+ "action": "admin_create_user",
143
+ "user_pool_id": user_pool_id,
144
+ "user_name": user.email,
145
+ "retry_count": retry_count,
146
+ }
147
+ )
148
+ retry_count += 1
149
+ return self.admin_create_user(
150
+ user_pool_id=user_pool_id,
151
+ temp_password=None,
152
+ send_invitation=send_invitation,
153
+ user=user,
154
+ retry_count=retry_count,
155
+ )
156
+ raise
157
+ except Exception as e:
158
+ logger.error(f"Error: {e}")
159
+ raise
160
+
161
+ def admin_disable_user(
162
+ self, user_name: str, user_pool_id: str, reset_password: bool = True
163
+ ) -> dict:
164
+ """Disable a user in cognito"""
165
+ response = self.client.admin_disable_user(
166
+ UserPoolId=user_pool_id, Username=user_name
167
+ )
168
+
169
+ if reset_password:
170
+ self.admin_set_user_password(
171
+ user_name=user_name, user_pool_id=user_pool_id, password=None
172
+ )
173
+
174
+ return response
175
+
176
+ def admin_delete_user(self, user_name: str, user_pool_id: str) -> dict:
177
+ """Delete the user account"""
178
+
179
+ # we need to disbale a user first
180
+ self.admin_disable_user(
181
+ user_name=user_name, user_pool_id=user_pool_id, reset_password=False
182
+ )
183
+
184
+ response = self.client.admin_delete_user(
185
+ UserPoolId=user_pool_id, Username=user_name
186
+ )
187
+
188
+ return dict(response)
189
+
190
+ def admin_enable_user(
191
+ self, user_name: str, user_pool_id: str, reset_password: bool = True
192
+ ) -> dict:
193
+ """Enable the user account"""
194
+ response = self.client.admin_enable_user(
195
+ UserPoolId=user_pool_id, Username=user_name
196
+ )
197
+
198
+ if reset_password:
199
+ # reset the password
200
+ self.admin_set_user_password(
201
+ user_name=user_name, user_pool_id=user_pool_id, password=None
202
+ )
203
+ return response
204
+
205
+ def admin_set_user_password(
206
+ self, user_name, password: str | None, user_pool_id, is_permanent=True
207
+ ) -> dict:
208
+ """Set a user password"""
209
+
210
+ if not password:
211
+ password = StringUtility.generate_random_password(15)
212
+ logger.debug(
213
+ {
214
+ "action": "admin_set_user_password",
215
+ "UserPoolId": user_pool_id,
216
+ "Username": user_name,
217
+ "Password": "****************",
218
+ "Permanent": is_permanent,
219
+ }
220
+ )
221
+
222
+ for i in range(5):
223
+ try:
224
+ response = self.client.admin_set_user_password(
225
+ UserPoolId=user_pool_id,
226
+ Username=user_name,
227
+ Password=password,
228
+ Permanent=is_permanent,
229
+ )
230
+ break
231
+ except Exception as e: # pylint: disable=w0718
232
+ time.sleep(5 * i + 1)
233
+ logger.error(f"Error: {e}")
234
+ if i >= 4:
235
+ raise e
236
+
237
+ return response
238
+
239
+ def update_user_account(self, *, user_pool_id: str, user: CognitoUser) -> dict:
240
+ """
241
+ Update the cognito user account
242
+ """
243
+ user_attributes = self.__set_user_attributes(user=user)
244
+
245
+ if user.cognito_user_name is None:
246
+ raise ValueError("User cognito user name is required")
247
+
248
+ response = self.client.admin_update_user_attributes(
249
+ UserPoolId=f"{user_pool_id}",
250
+ Username=f"{user.cognito_user_name}",
251
+ UserAttributes=user_attributes,
252
+ ClientMetadata={"string": "string"},
253
+ )
254
+ return response
255
+
256
+ def sign_up_cognito_user(self, email, password, client_id) -> dict | None:
257
+ """
258
+ This is only allowed if the admin only flag is not being inforced.
259
+ Under most circumstatnces we won't have this enabled
260
+ """
261
+ email = self.__format_email(email=email)
262
+ try:
263
+ # Create the user in Cognito
264
+ response = self.client.sign_up(
265
+ ClientId=client_id,
266
+ Username=email,
267
+ Password=password,
268
+ UserAttributes=[{"Name": "email", "Value": email}],
269
+ )
270
+
271
+ logger.debug(
272
+ f"User {email} created successfully. Confirmation code sent to {email}."
273
+ )
274
+ return dict(response)
275
+
276
+ except self.client.exceptions.UsernameExistsException as e:
277
+ logger.error(f"Error: {e.response['Error']['Message']}")
278
+ logger.error(
279
+ f"The username {email} already exists. Please choose a different username."
280
+ )
281
+ return None
282
+
283
+ except self.client.exceptions.InvalidPasswordException as e:
284
+ logger.error(f"Error: {e.response['Error']['Message']}")
285
+ logger.error(
286
+ "Password does not meet the requirements. Please choose a stronger password."
287
+ )
288
+ return None
289
+
290
+ except Exception as e: # pylint: disable=w0718
291
+ logger.error(f"Error: {e}")
292
+ return None
293
+
294
+ def authenticate_user_pass_auth(
295
+ self, username, password, client_id
296
+ ) -> tuple[str, str, str]:
297
+ """
298
+ Login with the username/passwrod combo + client_id
299
+ Returns:
300
+ Tuple: id_token, access_token, refresh_token
301
+ Use the id_token as the jwt
302
+ Use the access_token if you are directly accessing aws resources
303
+ Use the refresh_token if you are attempting to get a 'refreshed' jwt token
304
+ """
305
+ # Initiate the authentication process and get the session
306
+ auth_response = self.client.initiate_auth(
307
+ ClientId=client_id,
308
+ AuthFlow="USER_PASSWORD_AUTH",
309
+ AuthParameters={"USERNAME": username, "PASSWORD": password},
310
+ )
311
+
312
+ if "ChallengeName" in auth_response:
313
+ raise RuntimeError("New password required before a token can be provided")
314
+
315
+ # Extract the session tokens
316
+ id_token = auth_response["AuthenticationResult"]["IdToken"]
317
+ access_token = auth_response["AuthenticationResult"]["AccessToken"]
318
+ refresh_token = auth_response["AuthenticationResult"]["RefreshToken"]
319
+
320
+ return id_token, access_token, refresh_token
321
+
322
+ def create_client_app_machine_to_machine(
323
+ self,
324
+ user_pool_id,
325
+ client_name,
326
+ id_token_time_out=60,
327
+ id_token_units: Literal["days", "hours", "minutes", "seconds"] = "minutes",
328
+ access_token_time_out=60,
329
+ access_token_units: Literal["days", "hours", "minutes", "seconds"] = "minutes",
330
+ refresh_token_time_out=60,
331
+ refresh_token_units: Literal["days", "hours", "minutes", "seconds"] = "minutes",
332
+ ) -> dict:
333
+ # valid units: 'seconds'|'minutes'|'hours'|'days'
334
+
335
+ response = self.client.create_user_pool_client(
336
+ UserPoolId=f"{user_pool_id}",
337
+ ClientName=f"{client_name}",
338
+ GenerateSecret=True,
339
+ RefreshTokenValidity=refresh_token_time_out,
340
+ AccessTokenValidity=access_token_time_out,
341
+ IdTokenValidity=id_token_time_out,
342
+ TokenValidityUnits={
343
+ "AccessToken": access_token_units,
344
+ "IdToken": id_token_units,
345
+ "RefreshToken": refresh_token_units,
346
+ },
347
+ # ReadAttributes=[
348
+ # 'string',
349
+ # ],
350
+ # WriteAttributes=[
351
+ # 'string',
352
+ # ],
353
+ # ExplicitAuthFlows=[
354
+ # 'ADMIN_NO_SRP_AUTH'|'CUSTOM_AUTH_FLOW_ONLY'|'USER_PASSWORD_AUTH'|'ALLOW_ADMIN_USER_PASSWORD_AUTH'|'ALLOW_CUSTOM_AUTH'|'ALLOW_USER_PASSWORD_AUTH'|'ALLOW_USER_SRP_AUTH'|'ALLOW_REFRESH_TOKEN_AUTH',
355
+ # ],
356
+ # SupportedIdentityProviders=[
357
+ # 'string',
358
+ # ],
359
+ # CallbackURLs=[
360
+ # 'string',
361
+ # ],
362
+ # LogoutURLs=[
363
+ # 'string',
364
+ # ],
365
+ # DefaultRedirectURI='string',
366
+ AllowedOAuthFlows=["client_credentials"],
367
+ AllowedOAuthScopes=[
368
+ "string",
369
+ ],
370
+ AllowedOAuthFlowsUserPoolClient=True,
371
+ # AnalyticsConfiguration={
372
+ # 'ApplicationId': 'string',
373
+ # 'ApplicationArn': 'string',
374
+ # 'RoleArn': 'string',
375
+ # 'ExternalId': 'string',
376
+ # 'UserDataShared': True|False
377
+ # },
378
+ # PreventUserExistenceErrors='LEGACY'|'ENABLED',
379
+ EnableTokenRevocation=True,
380
+ # EnablePropagateAdditionalUserContextData=True|False,
381
+ # AuthSessionValidity=123
382
+ )
383
+
384
+ return dict(response)
385
+
386
+ def search_cognito(self, email: str, user_pool_id: str) -> dict:
387
+ """Search cognito for an existing user"""
388
+
389
+ email = self.__format_email(email=email) or ""
390
+ filter_string = f'email = "{email}"'
391
+
392
+ # Call the admin_list_users method with the filter
393
+ response = self.client.list_users(UserPoolId=user_pool_id, Filter=filter_string)
394
+
395
+ return dict(response)
396
+
397
+ def __set_user_attributes(self, *, user: CognitoUser) -> List[dict]:
398
+ """
399
+ Constructs a list of user attributes for Cognito based on the provided user object.
400
+
401
+ Args:
402
+ user (CognitoUser): The user object containing attributes to set.
403
+
404
+ Returns:
405
+ List[dict]: A list of attribute dictionaries for Cognito.
406
+ """
407
+ user_attributes: List[Dict[str, Any]] = [
408
+ {"Name": "email", "Value": str(user.email).lower()}
409
+ ]
410
+
411
+ user_attributes.append({"Name": "email_verified", "Value": "true"})
412
+
413
+ if user.first_name is not None:
414
+ user_attributes.append({"Name": "given_name", "Value": user.first_name})
415
+
416
+ if user.last_name is not None:
417
+ user_attributes.append({"Name": "family_name", "Value": user.last_name})
418
+
419
+ if self.custom_attributes:
420
+ if user.id is not None:
421
+ user_attributes.append(
422
+ {
423
+ "Name": self.custom_attributes.user_id_custom_attribute,
424
+ "Value": user.id,
425
+ }
426
+ )
427
+
428
+ if user.roles is not None:
429
+ roles: str = (
430
+ ",".join(user.roles) if isinstance(user.roles, list) else user.roles
431
+ )
432
+ user_attributes.append(
433
+ {
434
+ "Name": self.custom_attributes.user_roles_custom_attribute,
435
+ "Value": roles,
436
+ }
437
+ )
438
+
439
+ if user.tenant_id is not None:
440
+ user_attributes.append(
441
+ {
442
+ "Name": self.custom_attributes.tenant_id_custom_attribute,
443
+ "Value": user.tenant_id,
444
+ }
445
+ )
446
+
447
+ return user_attributes
448
+
449
+ def map(self, cognito_response: dict) -> CognitoUser:
450
+ """Map the cognito response to a user object"""
451
+ user = CognitoUser()
452
+ # this is the internal Cognito ID that get's generated
453
+ user.cognito_user_name = self.get_cognito_attribute(
454
+ cognito_response, "Username"
455
+ )
456
+ user.email = self.get_cognito_attribute(cognito_response, "email", None)
457
+ user.first_name = self.get_cognito_attribute(
458
+ cognito_response, "given_name", None
459
+ )
460
+ user.last_name = self.get_cognito_attribute(
461
+ cognito_response, "family_name", None
462
+ )
463
+ if self.custom_attributes:
464
+ user.id = self.get_cognito_attribute(
465
+ cognito_response, self.custom_attributes.user_id_custom_attribute, None
466
+ )
467
+ user.tenant_id = self.get_cognito_attribute(
468
+ cognito_response,
469
+ self.custom_attributes.tenant_id_custom_attribute,
470
+ None,
471
+ )
472
+
473
+ roles: str | None | List[str] = self.get_cognito_attribute(
474
+ cognito_response,
475
+ self.custom_attributes.user_roles_custom_attribute,
476
+ None,
477
+ )
478
+ else:
479
+ user.id = self.get_cognito_attribute(cognito_response, "sub", None)
480
+ roles = self.get_cognito_attribute(cognito_response, "cognito:groups", None)
481
+
482
+ if roles is None:
483
+ roles = []
484
+ if isinstance(roles, str):
485
+ roles = roles.split(",")
486
+ user.roles = roles
487
+ return user
488
+
489
+ def get_cognito_attribute(
490
+ self, response: dict, name: str, default: Optional[str] = None
491
+ ) -> Optional[str]:
492
+ if name in response:
493
+ return response.get(name, default)
494
+
495
+ attributes = response.get("Attributes", [])
496
+ attribute = DictionaryUtilitiy.find_dict_by_name(attributes, "Name", name)
497
+ if attribute and isinstance(attribute, list):
498
+ return str(attribute[0].get("Value", default))
499
+ return default
500
+
501
+ def __format_email(self, email: str | None) -> str | None:
502
+ """
503
+ Formats an email address to be used in Cognito user pools. Converts to lowercase
504
+ if self.auto_lower_case_email_addresses is set to true (the default)
505
+
506
+ Args:
507
+ email (str | None): The email address to format.
508
+
509
+ Returns:
510
+ str | None: The formatted email address, or None if input is None.
511
+ """
512
+ if self.auto_lower_case_email_addresses:
513
+ return None if email is None else str(email).lower()
514
+ return email
@@ -0,0 +1,21 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+
8
+ class JwksCache:
9
+ """A JWT Caching object"""
10
+
11
+ def __init__(self):
12
+ self.__cache = {}
13
+
14
+ @property
15
+ def cache(self):
16
+ """The Cache"""
17
+ return self.__cache
18
+
19
+ @cache.setter
20
+ def cache(self, value):
21
+ self.__cache = value
@@ -0,0 +1,27 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ from typing import Optional
8
+ from boto3_assist.models.serializable_model import SerializableModel
9
+
10
+
11
+ class CognitoUser(SerializableModel):
12
+ """A generic way to represent a cognito user"""
13
+
14
+ def __init__(
15
+ self,
16
+ id: Optional[str] = None, # pylint: disable=w0622
17
+ ) -> None:
18
+ super().__init__()
19
+ self.id: Optional[str] = id
20
+ self.first_name: Optional[str] = None
21
+ self.last_name: Optional[str] = None
22
+ self.email: Optional[str] = None
23
+ self.tenant_id: Optional[str] = None
24
+ self.status: Optional[str] = None
25
+ self.company_name: Optional[str] = None
26
+ self.roles: list[str] = []
27
+ self.cognito_user_name: str | None = None