first commit

This commit is contained in:
2025-11-30 00:07:24 +01:00
commit b5e642aecb
78 changed files with 15162 additions and 0 deletions

229
test_client.py Executable file
View File

@ -0,0 +1,229 @@
#!/usr/bin/env python3
"""
OIDC Test Client
Demonstriert den vollständigen Authorization Code Flow
"""
from flask import Flask, request, redirect, session, render_template_string
import requests
from urllib.parse import urlencode
import secrets
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
app.config['SESSION_COOKIE_SECURE'] = False
# OIDC Server Konfiguration
OIDC_ISSUER = "http://localhost:5000"
CLIENT_ID = "test-client"
CLIENT_SECRET = "test-secret"
REDIRECT_URI = "http://localhost:8080/callback"
# In-Memory Store für OAuth States (überlebt Debug-Reloads nicht, aber reicht für Tests)
oauth_states = {}
# HTML Template
HOME_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OIDC Test Client</title>
<link rel="stylesheet" href="http://localhost:5000/static/styles.css">
</head>
<body>
<div class="container">
<header>
<h1>🧪 OIDC Test Client</h1>
<p>Test-Anwendung für den Authorization Code Flow</p>
</header>
{% if user_info %}
<div class="modal-content" style="max-width: 600px; margin: 0 auto;">
<h2 style="color: var(--text-main); margin-bottom: 24px;">✅ Login erfolgreich!</h2>
<div class="stat-card" style="margin-bottom: 20px;">
<h3>User Information</h3>
<div style="margin-top: 16px;">
<p><strong>Subject ID:</strong> {{ user_info.sub }}</p>
<p><strong>Name:</strong> {{ user_info.name }}</p>
<p><strong>Email:</strong> {{ user_info.email }}</p>
<p><strong>Username:</strong> {{ user_info.preferred_username }}</p>
</div>
</div>
<div class="stat-card" style="margin-bottom: 20px;">
<h3>Access Token</h3>
<div style="margin-top: 16px; word-break: break-all; font-family: monospace; font-size: 0.85rem;">
{{ tokens.access_token }}
</div>
<p style="margin-top: 12px; color: var(--text-secondary); font-size: 0.9rem;">
Expires in: {{ tokens.expires_in }} seconds
</p>
</div>
<div class="stat-card">
<h3>ID Token (JWT)</h3>
<details>
<summary style="cursor: pointer; color: var(--primary); margin-bottom: 12px;">Token anzeigen</summary>
<div style="word-break: break-all; font-family: monospace; font-size: 0.85rem; margin-top: 12px;">
{{ tokens.id_token }}
</div>
</details>
</div>
<div style="text-align: center; margin-top: 24px;">
<a href="/logout">
<button class="danger">Logout</button>
</a>
</div>
</div>
{% else %}
<div class="modal-content" style="max-width: 500px; margin: 0 auto; text-align: center;">
<div style="margin: 40px 0;">
<svg style="width: 80px; height: 80px; stroke: var(--primary); margin-bottom: 24px;" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" />
</svg>
<h2 style="color: var(--text-main); margin-bottom: 16px;">Nicht eingeloggt</h2>
<p style="color: var(--text-secondary); margin-bottom: 32px;">
Teste den OIDC Authorization Code Flow
</p>
<a href="/login">
<button style="font-size: 1.1rem; padding: 14px 32px;">
Mit OIDC einloggen →
</button>
</a>
</div>
<div style="margin-top: 40px; padding-top: 24px; border-top: 2px solid var(--border-main); text-align: left;">
<h3 style="color: var(--text-main); margin-bottom: 16px;">Flow-Ablauf:</h3>
<ol style="color: var(--text-secondary); line-height: 1.8;">
<li>Klick auf "Mit OIDC einloggen"</li>
<li>Weiterleitung zum OIDC Server (localhost:5000)</li>
<li>Login mit deinen Credentials</li>
<li>Authorization Code wird zurückgegeben</li>
<li>Client tauscht Code gegen Tokens</li>
<li>UserInfo wird abgerufen</li>
</ol>
</div>
</div>
{% endif %}
</div>
</body>
</html>
"""
@app.route('/')
def index():
"""Startseite"""
user_info = session.get('user_info')
tokens = session.get('tokens')
return render_template_string(
HOME_TEMPLATE,
user_info=user_info,
tokens=tokens
)
@app.route('/login')
def login():
"""Startet den Authorization Flow"""
# State für CSRF-Protection generieren
state = secrets.token_urlsafe(32)
# State in In-Memory Store speichern
oauth_states[state] = True
# Authorization URL bauen
auth_params = {
'client_id': CLIENT_ID,
'redirect_uri': REDIRECT_URI,
'response_type': 'code',
'scope': 'openid profile email',
'state': state
}
auth_url = f"{OIDC_ISSUER}/authorize?{urlencode(auth_params)}"
return redirect(auth_url)
@app.route('/callback')
def callback():
"""Callback Endpoint - empfängt Authorization Code"""
# Authorization Code und State aus Query Params
code = request.args.get('code')
state = request.args.get('state')
# State validieren (CSRF-Protection)
if not state or state not in oauth_states:
return f"Invalid state parameter. State: {state}, Valid states: {list(oauth_states.keys())}", 400
# State verbrauchen (einmalige Verwendung)
del oauth_states[state]
if not code:
error = request.args.get('error')
error_description = request.args.get('error_description', '')
return f"Authorization failed: {error} - {error_description}", 400
# Authorization Code gegen Tokens tauschen
token_data = {
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': REDIRECT_URI,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET
}
try:
token_response = requests.post(f"{OIDC_ISSUER}/token", data=token_data)
token_response.raise_for_status()
tokens = token_response.json()
except requests.RequestException as e:
return f"Token exchange failed: {str(e)}", 500
# UserInfo mit Access Token abrufen
try:
userinfo_response = requests.get(
f"{OIDC_ISSUER}/userinfo",
headers={'Authorization': f"Bearer {tokens['access_token']}"}
)
userinfo_response.raise_for_status()
user_info = userinfo_response.json()
except requests.RequestException as e:
return f"UserInfo request failed: {str(e)}", 500
# In Session speichern
session['tokens'] = tokens
session['user_info'] = user_info
# Zurück zur Startseite
return redirect('/')
@app.route('/logout')
def logout():
"""Logout - löscht Session"""
session.clear()
return redirect('/')
if __name__ == '__main__':
print("=" * 60)
print("🧪 OIDC Test Client gestartet")
print("=" * 60)
print(f"Client URL: http://localhost:8080")
print(f"OIDC Server: {OIDC_ISSUER}")
print(f"Callback URI: {REDIRECT_URI}")
print("=" * 60)
print("\nÖffne im Browser: http://localhost:8080")
print("=" * 60)
app.run(host='0.0.0.0', port=8080, debug=True)