Init
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
8
Dockerfile
Normal file
8
Dockerfile
Normal 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
9
docker-compose.yml
Normal 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
1
log/ts_REGISTER.log
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1648187413851
|
||||||
1
log/ts_UPDATE_PROFILE.log
Normal file
1
log/ts_UPDATE_PROFILE.log
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1648609184265
|
||||||
192
notifier.py
Normal file
192
notifier.py
Normal 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
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
python-keycloak
|
||||||
|
datetime
|
||||||
|
schedule
|
||||||
|
python-dotenv
|
||||||
Reference in New Issue
Block a user