diff --git a/.gitignore b/.gitignore index 82f9275..666bce7 100644 --- a/.gitignore +++ b/.gitignore @@ -160,3 +160,7 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +debug_app_registrations.json +app_registrations.html +.env +.env diff --git a/aio.py b/aio.py index b3e8d62..c4b67ff 100644 --- a/aio.py +++ b/aio.py @@ -78,6 +78,10 @@ def sort_app_registrations(app_registrations): if app["passwordCredentials"]: expiry_date_str = app["passwordCredentials"][0]["endDateTime"] try: + if '.' in expiry_date_str: + expiry_date_str = expiry_date_str.split('.')[0] + '.' + expiry_date_str.split('.')[1][:6] + 'Z' + if expiry_date_str.endswith('ZZ'): + expiry_date_str = expiry_date_str[:-1] expiry_date = datetime.strptime(expiry_date_str, '%Y-%m-%dT%H:%M:%S.%fZ').replace(tzinfo=timezone.utc) except ValueError: expiry_date = datetime.strptime(expiry_date_str, '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) @@ -183,6 +187,10 @@ def generate_html(app_registrations): expiry_date = password_credentials[0].get('endDateTime') if expiry_date: try: + if '.' in expiry_date: + expiry_date = expiry_date.split('.')[0] + '.' + expiry_date.split('.')[1][:6] + 'Z' + if expiry_date.endswith('ZZ'): + expiry_date = expiry_date[:-1] expiry_date = datetime.strptime(expiry_date, '%Y-%m-%dT%H:%M:%S.%fZ').replace(tzinfo=timezone.utc) except ValueError: expiry_date = datetime.strptime(expiry_date, '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) @@ -228,4 +236,4 @@ if __name__ == "__main__": # Write to HTML file for inspection html_output = generate_html(sorted_app_registrations) with open('app_registrations.html', 'w') as f: - f.write(html_output) + f.write(html_output) \ No newline at end of file diff --git a/azure_client.py b/azure_client.py index f5a7c1a..c4b67ff 100644 --- a/azure_client.py +++ b/azure_client.py @@ -4,6 +4,7 @@ import logging import json import msal import requests +from datetime import datetime, timezone # Load environment variables load_dotenv() @@ -71,8 +72,168 @@ def get_app_registrations(): logging.error(f"Error fetching app registrations: {e}") return [] +def sort_app_registrations(app_registrations): + current_date = datetime.now(timezone.utc) + for app in app_registrations: + if app["passwordCredentials"]: + expiry_date_str = app["passwordCredentials"][0]["endDateTime"] + try: + if '.' in expiry_date_str: + expiry_date_str = expiry_date_str.split('.')[0] + '.' + expiry_date_str.split('.')[1][:6] + 'Z' + if expiry_date_str.endswith('ZZ'): + expiry_date_str = expiry_date_str[:-1] + expiry_date = datetime.strptime(expiry_date_str, '%Y-%m-%dT%H:%M:%S.%fZ').replace(tzinfo=timezone.utc) + except ValueError: + expiry_date = datetime.strptime(expiry_date_str, '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) + days_to_expiry = (expiry_date - current_date).days + app["days_to_expiry"] = days_to_expiry + app["expiry_date"] = expiry_date.isoformat() + else: + app["days_to_expiry"] = None + app["expiry_date"] = None + + sorted_apps = sorted(app_registrations, key=lambda x: (x["days_to_expiry"] is None, x["days_to_expiry"]), reverse=False) + return sorted_apps + +def generate_html(app_registrations): + current_time = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S') + html = f""" + + + App Registrations + + + +
+

Azure App Registration Expiry Notification

+

This is an automated notification regarding expiring Azure App Registrations that you own or manage.

+ +

Why am I receiving this?
+ You are receiving this email because you are listed as an owner of one or more Azure App Registrations that are approaching their expiration date or have already expired.

+ +

Required Actions:

+ + +

Color Coding:

+ + +

If you need assistance, please contact the IT Support team.

+
+ +

App Registrations

+

Exported on: {current_time}

+ + + + + + + + """ + + for app in app_registrations: + password_credentials = app.get('passwordCredentials', []) + if not password_credentials: + continue + + expiry_date = password_credentials[0].get('endDateTime') + if expiry_date: + try: + if '.' in expiry_date: + expiry_date = expiry_date.split('.')[0] + '.' + expiry_date.split('.')[1][:6] + 'Z' + if expiry_date.endswith('ZZ'): + expiry_date = expiry_date[:-1] + expiry_date = datetime.strptime(expiry_date, '%Y-%m-%dT%H:%M:%S.%fZ').replace(tzinfo=timezone.utc) + except ValueError: + expiry_date = datetime.strptime(expiry_date, '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) + days_to_expiry = (expiry_date - datetime.now(timezone.utc)).days + + if days_to_expiry > 30: + color_class = "green" + elif 7 < days_to_expiry <= 30: + color_class = "yellow" + elif 1 <= days_to_expiry <= 7: + color_class = "orange" + else: + color_class = "red" + days_to_expiry = "EXPIRED" + + owners = app.get('owners', []) + owner_upns = [owner.get('userPrincipalName') for owner in owners if owner.get('userPrincipalName')] + owner_list = ', '.join(owner_upns) if owner_upns else 'No owners' + + html += f""" + + + + + + + """ + + html += """ +
Display NameExpiry DateDays to ExpiryOwners
{app['displayName']}{expiry_date.strftime('%Y-%m-%d')}{days_to_expiry}{owner_list}
+ + + """ + + return html + if __name__ == "__main__": app_registrations = get_app_registrations() + sorted_app_registrations = sort_app_registrations(app_registrations) # Write to JSON file for inspection with open('debug_app_registrations.json', 'w') as f: - json.dump(app_registrations, f, indent=2) \ No newline at end of file + json.dump(sorted_app_registrations, f, indent=2) + # Write to HTML file for inspection + html_output = generate_html(sorted_app_registrations) + with open('app_registrations.html', 'w') as f: + f.write(html_output) \ No newline at end of file diff --git a/data_export.py b/data_export.py index ede24aa..ab37fbd 100644 --- a/data_export.py +++ b/data_export.py @@ -5,19 +5,19 @@ from datetime import datetime # Configure logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') -def write_to_json(app_registrations, filename='app_registrations.json'): - """ - Write app registration data to a JSON file. +# def write_to_json(app_registrations, filename='app_registrations.json'): +# """ +# Write app registration data to a JSON file. - :param app_registrations: List of app registration data - :param filename: Name of the JSON file to write to - """ - try: - with open(filename, 'w') as f: - json.dump(app_registrations, f, indent=4) - logging.info(f"App registration data successfully written to {filename}") - except Exception as e: - logging.error(f"Failed to write app registration data to {filename}: {e}") +# :param app_registrations: List of app registration data +# :param filename: Name of the JSON file to write to +# """ +# try: +# with open(filename, 'w') as f: +# json.dump(app_registrations, f, indent=4) +# logging.info(f"App registration data successfully written to {filename}") +# except Exception as e: +# logging.error(f"Failed to write app registration data to {filename}: {e}") def generate_html(app_registrations): """