This commit is contained in:
2024-02-05 06:11:53 +01:00
parent b10a52cdf5
commit c3774e9ac5
7 changed files with 216 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.env

8
Dockerfile Normal file
View File

@@ -0,0 +1,8 @@
FROM python:3
COPY . /
RUN \
pip install --no-cache-dir -U -r requirements.txt
CMD ["python3", "notifier.py"]

9
docker-compose.yml Normal file
View File

@@ -0,0 +1,9 @@
version: '3.2'
services:
keycloak-notifier:
build: .
container_name: keycloak-notifier
restart: unless-stopped
environment:
- TIMEZONE=Europe/Berlin

1
log/ts_REGISTER.log Normal file
View File

@@ -0,0 +1 @@
1648187413851

View File

@@ -0,0 +1 @@
1648609184265

192
notifier.py Normal file
View File

@@ -0,0 +1,192 @@
import requests
import os
import time
import smtplib
import schedule
import logging
import sys
from keycloak import KeycloakAdmin
from time import sleep
from datetime import datetime
from email.message import EmailMessage
from dotenv import load_dotenv
log = logging.getLogger('notifier')
log.setLevel("INFO")
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(asctime)s : %(levelname)s : %(name)s : %(message)s'))
log.addHandler(handler)
load_dotenv()
# Keycloak credentials
keycloak_username = os.getenv('KEYCLOAK_USERNAME')
keycloak_password = os.getenv('KEYCLOAK_PASSWORD')
keycloak_url = os.getenv('KEYCLOAK_URL')
keycloak_realm = os.getenv('KEYCLOAK_REALM')
keycloak_admin = KeycloakAdmin(
server_url=keycloak_url,
username=keycloak_username,
password=keycloak_password,
realm_name=keycloak_realm,
verify=True)
keycloak_data = {
'username': keycloak_username,
'password': keycloak_password,
'url': keycloak_url,
'realm': keycloak_realm,
'admin': keycloak_admin
}
def get_user(user_id):
user = keycloak_data['admin'].get_user(user_id)
return user
def get_keycloak_token():
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
}
data = {
'username': keycloak_data['username'],
'password': keycloak_data['password'],
'grant_type': 'password',
'client_id': 'admin-cli'
}
try:
response = requests.post(keycloak_data['url'] + 'realms/' + keycloak_data['realm'] +
'/protocol/openid-connect/token', headers=headers, data=data)
except Exception as e:
log.error(e)
log.info("Something went wrong. Sleeping it off for 10s")
sleep(10)
token = response.json()['access_token']
return token
def send_mail(message, event):
# SMTP configuration
sender = os.getenv('SMTP_SENDER')
receiver = os.getenv('SMTP_RECEIVER')
password = os.getenv('SMTP_PASSWORD')
host = os.getenv('SMTP_HOST')
port = os.getenv('SMTP_PORT')
# Email configuration
msg = EmailMessage()
msg.set_content(message)
msg['Subject'] = 'New Keycloak Event: ' + event
msg['From'] = sender
msg['To'] = receiver
# Start TLS and send email
server = smtplib.SMTP_SSL(host, port)
server.login(sender, password)
server.sendmail(
sender,
receiver,
str(msg)
)
server.quit()
def get_keycloak_events(token, event_type):
headers = {
'Accept': 'application/json',
'Authorization': 'Bearer ' + str(token),
}
response = requests.get(keycloak_data['url'] + 'admin/realms/' +
keycloak_data['realm'] + '/events?type=' + event_type, headers=headers)
if response.status_code == 200:
events = response.json()
# read a text file as a list of lines
# find the last line, change to a file you have
tsfile = './log/ts_' + event_type + '.log'
f = open(tsfile, "r")
line_list = f.readlines()
f.close()
last_timestamp = line_list[-1]
events_total = 0
events_relevant = 0
events_notif = 0
for event in events:
events_total += 1
try:
time = event.get('time')
timestring = str(time or '1000000')
unixtimestring = timestring[:-3]
human_time = str(datetime.fromtimestamp(int(unixtimestring)).strftime('%Y-%m-%d %H:%M:%S') or '')
user_id = event.get('userId')
if(event_type == 'REGISTER'):
events_relevant += 1
email = str(event.get('details').get('email') or 'n/A')
first_name = str(event.get('details').get('first_name') or 'n/A')
last_name = str(event.get('details').get('last_name') or 'n/A')
username = str(event.get('details').get('username') or 'n/A')
message = event_type + ': ' + first_name + ' ' + last_name + ' / ' + username + ' at' + \
' Time: ' + human_time + ' Email: ' + email
elif (event_type == 'UPDATE_PROFILE'):
events_relevant += 1
if(event.get('details').get('updated_email')):
message = event_type + ': ' + user_id + ' changed email from ' + str(event.get('details').get('previous_email') or 'n/A') \
+ ' to ' + event.get('details').get('updated_email') + ' at ' + human_time
else:
continue
f = open(tsfile, "r")
line_list = f.readlines()
f.close()
last_timestamp_new = line_list[-1]
if time > int(last_timestamp):
events_notif += 1
log.info("Sending Email: " + message)
send_mail(message, event_type)
log.info("Email sent.")
if time > int(last_timestamp_new):
log.info("Updating file.")
with open(tsfile, "w") as ts:
ts.write(str(time))
log.info("Updated file.")
sleep(2) # Time in seconds.dd
except Exception as e:
log.warning(e)
continue
log.info("Total events checked : " + str(events_total or "0"))
log.info("Relevant events : " + str(events_relevant or "0"))
log.info("Notifications generated : " + str(events_notif or "0"))
else:
log.error('Error: ' + str(response.status_code))
if response.status_code == 401:
log.warning("Token probably expired. Terminating notifier to restart...")
sys.exit()
def job():
log.info("Starting job.")
get_keycloak_events(token, 'REGISTER')
get_keycloak_events(token, 'UPDATE_PROFILE')
log.info("Job's done!")
log.info("Grabbing token...")
token = get_keycloak_token()
log.info("Got token!")
schedule.every(1).minutes.do(job)
while True:
schedule.run_pending()
time.sleep(1)

4
requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
python-keycloak
datetime
schedule
python-dotenv