aio
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -160,3 +160,7 @@ cython_debug/
|
|||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# 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.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
debug_app_registrations.json
|
||||||
|
app_registrations.html
|
||||||
|
.env
|
||||||
|
.env
|
||||||
|
|||||||
10
aio.py
10
aio.py
@@ -78,6 +78,10 @@ def sort_app_registrations(app_registrations):
|
|||||||
if app["passwordCredentials"]:
|
if app["passwordCredentials"]:
|
||||||
expiry_date_str = app["passwordCredentials"][0]["endDateTime"]
|
expiry_date_str = app["passwordCredentials"][0]["endDateTime"]
|
||||||
try:
|
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)
|
expiry_date = datetime.strptime(expiry_date_str, '%Y-%m-%dT%H:%M:%S.%fZ').replace(tzinfo=timezone.utc)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
expiry_date = datetime.strptime(expiry_date_str, '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc)
|
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')
|
expiry_date = password_credentials[0].get('endDateTime')
|
||||||
if expiry_date:
|
if expiry_date:
|
||||||
try:
|
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)
|
expiry_date = datetime.strptime(expiry_date, '%Y-%m-%dT%H:%M:%S.%fZ').replace(tzinfo=timezone.utc)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
expiry_date = datetime.strptime(expiry_date, '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc)
|
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
|
# Write to HTML file for inspection
|
||||||
html_output = generate_html(sorted_app_registrations)
|
html_output = generate_html(sorted_app_registrations)
|
||||||
with open('app_registrations.html', 'w') as f:
|
with open('app_registrations.html', 'w') as f:
|
||||||
f.write(html_output)
|
f.write(html_output)
|
||||||
163
azure_client.py
163
azure_client.py
@@ -4,6 +4,7 @@ import logging
|
|||||||
import json
|
import json
|
||||||
import msal
|
import msal
|
||||||
import requests
|
import requests
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
# Load environment variables
|
# Load environment variables
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
@@ -71,8 +72,168 @@ def get_app_registrations():
|
|||||||
logging.error(f"Error fetching app registrations: {e}")
|
logging.error(f"Error fetching app registrations: {e}")
|
||||||
return []
|
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"""
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>App Registrations</title>
|
||||||
|
<style>
|
||||||
|
body {{
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 20px;
|
||||||
|
color: #333;
|
||||||
|
}}
|
||||||
|
.intro {{
|
||||||
|
margin-bottom: 20px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}}
|
||||||
|
table {{
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}}
|
||||||
|
th, td {{
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
}}
|
||||||
|
th {{
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}}
|
||||||
|
.green {{
|
||||||
|
background-color: #d4edda;
|
||||||
|
}}
|
||||||
|
.yellow {{
|
||||||
|
background-color: #fff3cd;
|
||||||
|
}}
|
||||||
|
.orange {{
|
||||||
|
background-color: #ffeeba;
|
||||||
|
}}
|
||||||
|
.red {{
|
||||||
|
background-color: #f8d7da;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="intro">
|
||||||
|
<h2>Azure App Registration Expiry Notification</h2>
|
||||||
|
<p>This is an automated notification regarding expiring Azure App Registrations that you own or manage.</p>
|
||||||
|
|
||||||
|
<p><strong>Why am I receiving this?</strong><br>
|
||||||
|
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.</p>
|
||||||
|
|
||||||
|
<p><strong>Required Actions:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>Review the list of app registrations below</li>
|
||||||
|
<li>For any expiring or expired registrations:
|
||||||
|
<ul>
|
||||||
|
<li>Verify if the app registration is still needed</li>
|
||||||
|
<li>If needed, renew the credentials before they expire</li>
|
||||||
|
<li>If not needed, consider removing the app registration</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Color Coding:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li style="background-color: #d4edda; padding: 3px;">Green: More than 30 days until expiry</li>
|
||||||
|
<li style="background-color: #fff3cd; padding: 3px;">Yellow: Between 8-30 days until expiry</li>
|
||||||
|
<li style="background-color: #ffeeba; padding: 3px;">Orange: 7 days or less until expiry</li>
|
||||||
|
<li style="background-color: #f8d7da; padding: 3px;">Red: Expired</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>If you need assistance, please contact the IT Support team.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>App Registrations</h1>
|
||||||
|
<p>Exported on: {current_time}</p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Display Name</th>
|
||||||
|
<th>Expiry Date</th>
|
||||||
|
<th>Days to Expiry</th>
|
||||||
|
<th>Owners</th>
|
||||||
|
</tr>
|
||||||
|
"""
|
||||||
|
|
||||||
|
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"""
|
||||||
|
<tr class="{color_class}">
|
||||||
|
<td>{app['displayName']}</td>
|
||||||
|
<td>{expiry_date.strftime('%Y-%m-%d')}</td>
|
||||||
|
<td>{days_to_expiry}</td>
|
||||||
|
<td>{owner_list}</td>
|
||||||
|
</tr>
|
||||||
|
"""
|
||||||
|
|
||||||
|
html += """
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
return html
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app_registrations = get_app_registrations()
|
app_registrations = get_app_registrations()
|
||||||
|
sorted_app_registrations = sort_app_registrations(app_registrations)
|
||||||
# Write to JSON file for inspection
|
# Write to JSON file for inspection
|
||||||
with open('debug_app_registrations.json', 'w') as f:
|
with open('debug_app_registrations.json', 'w') as f:
|
||||||
json.dump(app_registrations, f, indent=2)
|
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)
|
||||||
@@ -5,19 +5,19 @@ from datetime import datetime
|
|||||||
# Configure logging
|
# Configure logging
|
||||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
def write_to_json(app_registrations, filename='app_registrations.json'):
|
# def write_to_json(app_registrations, filename='app_registrations.json'):
|
||||||
"""
|
# """
|
||||||
Write app registration data to a JSON file.
|
# Write app registration data to a JSON file.
|
||||||
|
|
||||||
:param app_registrations: List of app registration data
|
# :param app_registrations: List of app registration data
|
||||||
:param filename: Name of the JSON file to write to
|
# :param filename: Name of the JSON file to write to
|
||||||
"""
|
# """
|
||||||
try:
|
# try:
|
||||||
with open(filename, 'w') as f:
|
# with open(filename, 'w') as f:
|
||||||
json.dump(app_registrations, f, indent=4)
|
# json.dump(app_registrations, f, indent=4)
|
||||||
logging.info(f"App registration data successfully written to {filename}")
|
# logging.info(f"App registration data successfully written to {filename}")
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
logging.error(f"Failed to write app registration data to {filename}: {e}")
|
# logging.error(f"Failed to write app registration data to {filename}: {e}")
|
||||||
|
|
||||||
def generate_html(app_registrations):
|
def generate_html(app_registrations):
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user