-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Brad Duncan
committed
Feb 26, 2024
1 parent
271b97d
commit 15f1cc1
Showing
9 changed files
with
236 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
name: Build | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
|
||
|
||
jobs: | ||
build: | ||
name: Build | ||
runs-on: ubuntu-latest | ||
permissions: read-all | ||
steps: | ||
- uses: actions/checkout@v2 | ||
with: | ||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis | ||
- uses: sonarsource/sonarqube-scan-action@master | ||
env: | ||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | ||
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} | ||
# If you wish to fail your job when the Quality Gate is red, uncomment the | ||
# following lines. This would typically be used to fail a deployment. | ||
# - uses: sonarsource/sonarqube-quality-gate-action@master | ||
# timeout-minutes: 5 | ||
# env: | ||
# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
venv/ | ||
.vscode/ | ||
__pycache__/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import os | ||
|
||
aws_region = os.getenv('AWS_REGION', '') | ||
cognito_user_pool_id = os.getenv('COGNITO_USER_POOL_ID', '') | ||
s3_bucket_name = os.getenv('S3_BUCKET_NAME', '') | ||
s3_key = os.getenv('S3_KEY', '') | ||
sns_topic_arn = os.getenv('SNS_TOPIC_ARN', '') | ||
aged_user_threshold_minutes = os.getenv('AGED_USER_THRESHOLD_MINUTES', '1') | ||
user_status = os.getenv('USER_STATUS', 'UNCONFIRMED,RESET_REQUIRED,FORCE_CHANGE_PASSWORD') | ||
delete_enabled = os.getenv('DELETE_ENABLED', 'False') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import boto3 | ||
from library import * | ||
from config import * | ||
from datetime import datetime | ||
import pytz | ||
|
||
# Initialize the Boto3 clients and environment variables | ||
cognito_client = boto3.client('cognito-idp', region_name=aws_region) | ||
s3_client = boto3.client('s3', region_name=aws_region) | ||
sns_client = boto3.client('sns', region_name=aws_region) | ||
|
||
# Caching the last processed time and deleted users | ||
last_processed_time_cache = {} | ||
deleted_users_cache = set() | ||
|
||
def main_handler(event, context): | ||
current_run_time = datetime.now(pytz.utc) | ||
delete_enabled = event.get('delete_enabled', 'false').lower() == 'true' | ||
|
||
# List all unconfirmed users, considering the last processed time from the cache | ||
unconfirmed_users = list_users(cognito_client, cognito_user_pool_id, aged_user_threshold_minutes, user_status, last_processed_time_cache) | ||
|
||
# Print the list of unconfirmed users | ||
print(f"{user_status} users older than {aged_user_threshold_minutes} minutes: {unconfirmed_users}") | ||
|
||
# Delete the unconfirmed users and collect the ones successfully deleted | ||
deleted_users = [user for user in unconfirmed_users if delete_users(cognito_client, cognito_user_pool_id, user, delete_enabled, deleted_users_cache)] | ||
|
||
if deleted_users: | ||
# Write the list of deleted users to S3 | ||
write_deleted_users_to_s3(s3_client, deleted_users, s3_bucket_name, s3_key) | ||
# Send an email notification with the list of deleted users | ||
send_email_notification(sns_client, sns_topic_arn, deleted_users) | ||
message = "Deleted users were processed and notifications were sent." | ||
|
||
# Update the last_processed_time_cache with the current run time only if users were deleted | ||
last_processed_time_cache[cognito_user_pool_id] = current_run_time | ||
else: | ||
message = "No unconfirmed users to delete or delete not enabled." | ||
|
||
return { | ||
'statusCode': 200, | ||
'body': message | ||
} | ||
|
||
if __name__ == '__main__': | ||
event = {} | ||
context = {} | ||
main_handler(event, context) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .cognito_manager import * | ||
from .file_manager import * | ||
from .notification_service import * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
from datetime import datetime, timedelta | ||
import pytz | ||
|
||
def list_users(cognito_client, user_pool_id, age_in_minutes, user_statuses, last_run_cache): | ||
""" | ||
List users from Cognito based on specified statuses and age, adjusted by the last processed time in cache. | ||
:param cognito_client: The Boto3 client for Cognito | ||
:param user_pool_id: The ID of the Cognito User Pool | ||
:param age_in_minutes: The age of the users in minutes to filter by | ||
:param user_statuses: Comma separated statuses of the users to filter by | ||
:param last_run_cache: A dictionary serving as cache to store the last run timestamp | ||
:return: A list of usernames of users with specified statuses older than 'age_in_minutes' or since last cache timestamp | ||
""" | ||
try: | ||
# Ensure age_in_minutes is an integer | ||
age_in_minutes = int(age_in_minutes) | ||
|
||
# Attempt to get the last run time from cache | ||
last_run_time = last_run_cache.get(user_pool_id) | ||
|
||
# Calculate the cutoff time | ||
if last_run_time: | ||
cutoff_time = last_run_time | ||
else: | ||
# If not available in cache, calculate cutoff time based on age_in_minutes | ||
cutoff_time = datetime.now(pytz.utc) - timedelta(minutes=age_in_minutes) | ||
|
||
# Initialise the list to store the filtered usernames | ||
aged_users = [] | ||
|
||
# Split the user_statuses into a list | ||
statuses = user_statuses.split(',') | ||
|
||
# Paginate through the list_users response for each status | ||
for status in statuses: | ||
paginator = cognito_client.get_paginator('list_users') | ||
for page in paginator.paginate(UserPoolId=user_pool_id, Filter=f'cognito:user_status="{status}"'): | ||
for user in page['Users']: | ||
user_creation_time = user['UserCreateDate'] | ||
|
||
# Ensure user_creation_time is timezone-aware and in UTC | ||
if user_creation_time.tzinfo is None or user_creation_time.tzinfo.utcoffset(user_creation_time) is None: | ||
user_creation_time = pytz.utc.localize(user_creation_time) | ||
|
||
# Compare the user creation time with the cutoff time | ||
if user_creation_time < cutoff_time: | ||
aged_users.append(user['Username']) | ||
|
||
return aged_users | ||
except Exception as e: | ||
print(f"Error listing users: {e}") | ||
return [] | ||
|
||
|
||
|
||
def delete_users(cognito_client, user_pool_id, username, delete_enabled, deleted_users_cache): | ||
""" | ||
Deletes a single user from the Cognito user pool if delete_enabled is true. | ||
:param cognito_client: Boto3 Cognito client | ||
:param user_pool_id: String identifier of the Cognito user pool | ||
:param username: String identifier of the user to be deleted | ||
:param delete_enabled: Boolean value to determine if the user should be deleted | ||
:param deleted_users_cache: Set to store usernames of deleted users | ||
:return: Boolean value indicating the success of the deletion | ||
""" | ||
if delete_enabled: | ||
try: | ||
# Check if the user has already been deleted | ||
if username in deleted_users_cache: | ||
print(f"User {username} has already been deleted.") | ||
return False | ||
|
||
cognito_client.admin_delete_user( | ||
UserPoolId=user_pool_id, | ||
Username=username | ||
) | ||
|
||
# Update cache after successful deletion | ||
deleted_users_cache.add(username) | ||
return True | ||
except Exception as e: | ||
print(f"Error deleting user {username}: {e}") | ||
return False | ||
else: | ||
print(f"Skipping deletion of user {username} as delete_enabled is not set to 'True'") | ||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
def write_deleted_users_to_s3(s3_client, deleted_users, bucket_name=None, s3_key=None): | ||
""" | ||
Writes the list of deleted users to a file in an S3 bucket. Skips writing if | ||
bucket_name or s3_key is not provided. | ||
:param s3_client: Boto3 S3 client | ||
:param deleted_users: List of usernames that were deleted | ||
:param bucket_name: The name of the S3 bucket where the file will be stored, optional | ||
:param s3_key: The S3 key (file path within the bucket) for the file, optional | ||
""" | ||
if not bucket_name or not s3_key: | ||
print(f"Skipping writing to S3 as either bucket_name or s3_key has not been set...") | ||
return | ||
|
||
# Convert the list of deleted users to a string, one username per line | ||
deleted_users_str = "\n".join(deleted_users) | ||
|
||
try: | ||
# Proceed to write the string to a file in S3 | ||
s3_client.put_object(Bucket=bucket_name, Key=s3_key, Body=deleted_users_str) | ||
print(f"Successfully wrote deleted users to {s3_key} in bucket {bucket_name}.") | ||
except Exception as e: | ||
print(f"Failed to write deleted users to S3: {e}") | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
def send_email_notification(sns_client, topic_arn, deleted_users): | ||
""" | ||
Sends an email notification with the list of deleted users via AWS SNS. | ||
:param sns_client: Boto3 SNS client | ||
:param topic_arn: The ARN of the SNS topic to publish the message to | ||
:param deleted_users: List of usernames that were deleted | ||
""" | ||
if not topic_arn: | ||
print("Skipping SNS notification as topic has not been configured...") | ||
return | ||
if not sns_client: | ||
raise ValueError("ERROR: sns_client not initialised") | ||
if not deleted_users: | ||
print("No users deleted, skipping SNS notification...") | ||
return | ||
|
||
# Creating the message | ||
message = "Deleted Users:\n" + "\n".join(deleted_users) | ||
|
||
# Sending the message | ||
try: | ||
response = sns_client.publish( | ||
TopicArn=topic_arn, | ||
Message=message, | ||
Subject='Notification of Deleted Users' | ||
) | ||
print(f"Message sent to SNS topic {topic_arn}. Message ID: {response['MessageId']}") | ||
except Exception as e: | ||
print(f"Failed to send notification due to an error: {e}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
sonar.projectKey=XargsUK_cognito-cleaner_AY3lY9z82k2cii8YoWbp |