Skip to content
Snippets Groups Projects
Commit c552f7f7 authored by Václav Bartoš's avatar Václav Bartoš
Browse files

load list of users from keycloak (configuration needed)

+ added requirements.txt
parent 38f34e83
Branches
Tags
No related merge requests found
# Example of minimal working WSGI script
from flask import Flask, render_template, request, make_response, redirect, flash
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired, Email
import requests
from datetime import datetime
import subprocess
app = Flask(__name__)
app.secret_key = "ASDF1234 - CHANGE ME!"
# *** Configuration ***
# TODO get this from config/environment
CA_CERT = "" # path to secrets/CA/ca.crt
KEYCLOAK_BASE_URL = "" # https://{{soctoolsproxy}}:12443
KEYCLOAK_ADMIN_PASSWORD = "" # take from secrets/passwords/keykloak_admin (Note: should be keycloak, not keykloak)
# *** Custom Jinja filters ***
def ts_to_str(ts):
return datetime.utcfromtimestamp(int(ts)).isoformat(sep=" ") # TODO Do Keycloak really use UTC timestamps?
app.jinja_env.filters["ts_to_str"] = ts_to_str
# *** Functions to call other APIs ***
def get_token():
"""Get admin's OIDC token from Keycloak - needed to perform any administrative API call"""
url = KEYCLOAK_BASE_URL + "/auth/realms/master/protocol/openid-connect/token"
data = {
"client_id": "admin-cli",
"username": "admin",
"password": KEYCLOAK_ADMIN_PASSWORD,
"grant_type": "password"
}
try:
resp = requests.post(url, data, verify=CA_CERT)
if resp.status_code != 200:
flash(f"ERROR: Can't get token for API access: ({resp.status_code}) {resp.text[:200]}", "error")
return None
return str(resp.json()['access_token'])
except Exception as e:
flash(f"ERROR: Can't get token for API access: {type(e).__name__}: {e}", "error")
return None
def get_users():
# Get list of users from Keycloak
url = KEYCLOAK_BASE_URL + "/auth/admin/realms/SOCTOOLS1/users"
token = get_token()
if token is None:
return [] # can't get token, error message is already flashed by get_token function
resp = requests.get(url, headers={'Authorization': 'Bearer ' + token}, verify=CA_CERT)
if not resp.ok:
flash(f"ERROR: Can't get list of users: ({resp.status_code}) {resp.text[:200]}", "error")
return []
try:
users = resp.json()
assert isinstance(users, list) and all(isinstance(o, dict) for o in users), ""
except (ValueError, AssertionError):
flash(f"ERROR: Can't get list of users: Unexpected content of response from Keycloak", "error")
return []
return users
# *** Flask endpoints and forms ***
class AddUserForm(FlaskForm):
username = StringField("Username", validators=[DataRequired()])
cn = StringField("Common name (CN)", validators=[DataRequired()])
firstname = StringField("First name", validators=[])
lastname = StringField("Last name", validators=[])
# TODO what about CN/DN - construct from first+last name or allow to redefine?
email = StringField("Email", validators=[DataRequired(), Email()])
# DN is constructed automatically from CN
@app.route("/", methods=["GET", "POST"])
def main():
# TODO Load existing users (from where?)
users = [{
"firstname": "User1",
"lastname": "SOC",
"username": "user1",
"email": "user1@example.org",
"DN": "CN=User1Soctools",
"CN": "User1Soctools",
},{
"firstname": "User2",
"lastname": "SOC",
"username": "user2",
"email": "user2@example.org",
"DN": "CN=User2Soctools",
"CN": "User2Soctools",
}]
# Add user
# Load existing users from Keycloak
users = get_users()
#print(users)
# Add user form
form_add_user = AddUserForm()
if form_add_user.validate_on_submit():
# TODO check that username doesn't exist, yet (and check validity, i.e. special characters etc.)
......
flask~=2.1.0
flask_wtf~=1.0.0
wtforms~=3.0.1
email-validator~=1.1.3
requests~=2.27.1
jinja2~=3.1.1
\ No newline at end of file
......@@ -20,21 +20,22 @@
<h1>SOCtools - User management</h1>
<table>
<tr><th>Username</th><th>First name</th><th>Last name</th><th>CN</th><th>email</th><th></th>
<tr><th>Username</th><th>First name</th><th>Last name</th><th>email</th><th>CN</th><th>DN</th><th>Time created</th><th></th>
{% for user in users %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.firstname }}</td>
<td>{{ user.lastname }}</td>
<td>{{ user.CN }}</td>
<td>{{ user.firstName }}</td>
<td>{{ user.lastName }}</td>
<td>{{ user.email }}</td>
<td>... {#TODO actions#}</td>
<td>{{ user.attributes.CN[0] }}</td>
<td>{{ user.attributes.DN[0] }}</td>
<td>{{ (user.createdTimestamp/1000)|ts_to_str }}</td>
<td>...</td>
</tr>
{% endfor %}
</table>
<p></p>
<h2>Add new user</h2>
<form action="{{ url_for("main") }}" method="POST">
{% if form_add_user.errors %}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment