pyobs-auth 2.0.0.dev6__tar.gz → 2.0.0.dev7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyobs_auth-2.0.0.dev6 → pyobs_auth-2.0.0.dev7}/PKG-INFO +1 -1
- pyobs_auth-2.0.0.dev7/pyobs_auth/templates/pyobs_auth/error.html +50 -0
- {pyobs_auth-2.0.0.dev6 → pyobs_auth-2.0.0.dev7}/pyobs_auth/views.py +17 -8
- {pyobs_auth-2.0.0.dev6 → pyobs_auth-2.0.0.dev7}/pyproject.toml +1 -1
- {pyobs_auth-2.0.0.dev6 → pyobs_auth-2.0.0.dev7}/.gitignore +0 -0
- {pyobs_auth-2.0.0.dev6 → pyobs_auth-2.0.0.dev7}/LICENSE +0 -0
- {pyobs_auth-2.0.0.dev6 → pyobs_auth-2.0.0.dev7}/pyobs_auth/__init__.py +0 -0
- {pyobs_auth-2.0.0.dev6 → pyobs_auth-2.0.0.dev7}/pyobs_auth/apps.py +0 -0
- {pyobs_auth-2.0.0.dev6 → pyobs_auth-2.0.0.dev7}/pyobs_auth/authentication.py +0 -0
- {pyobs_auth-2.0.0.dev6 → pyobs_auth-2.0.0.dev7}/pyobs_auth/client.py +0 -0
- {pyobs_auth-2.0.0.dev6 → pyobs_auth-2.0.0.dev7}/pyobs_auth/discovery.py +0 -0
- {pyobs_auth-2.0.0.dev6 → pyobs_auth-2.0.0.dev7}/pyobs_auth/py.typed +0 -0
- {pyobs_auth-2.0.0.dev6 → pyobs_auth-2.0.0.dev7}/pyobs_auth/settings.py +0 -0
- {pyobs_auth-2.0.0.dev6 → pyobs_auth-2.0.0.dev7}/pyobs_auth/urls.py +0 -0
- {pyobs_auth-2.0.0.dev6 → pyobs_auth-2.0.0.dev7}/pyobs_auth/validation.py +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Sign in failed</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: dark; }
|
|
9
|
+
body {
|
|
10
|
+
margin: 0;
|
|
11
|
+
min-height: 100vh;
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
background: #0d1117;
|
|
16
|
+
color: #c9d1d9;
|
|
17
|
+
font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
18
|
+
}
|
|
19
|
+
.card {
|
|
20
|
+
width: 100%;
|
|
21
|
+
max-width: 380px;
|
|
22
|
+
margin: 1rem;
|
|
23
|
+
padding: 1.5rem;
|
|
24
|
+
background: #161b22;
|
|
25
|
+
border: 1px solid #30363d;
|
|
26
|
+
border-radius: 0.5rem;
|
|
27
|
+
box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.4);
|
|
28
|
+
text-align: center;
|
|
29
|
+
}
|
|
30
|
+
h1 { font-size: 1.1rem; margin: 0 0 0.5rem; color: #f0f6fc; }
|
|
31
|
+
p.message { margin: 0 0 1.25rem; color: #8b949e; }
|
|
32
|
+
a.back {
|
|
33
|
+
display: block;
|
|
34
|
+
padding: 0.5rem 1rem;
|
|
35
|
+
border: 1px solid #30363d;
|
|
36
|
+
border-radius: 0.375rem;
|
|
37
|
+
color: #c9d1d9;
|
|
38
|
+
text-decoration: none;
|
|
39
|
+
}
|
|
40
|
+
a.back:hover { background: #21262d; }
|
|
41
|
+
</style>
|
|
42
|
+
</head>
|
|
43
|
+
<body>
|
|
44
|
+
<div class="card">
|
|
45
|
+
<h1>Sign in failed</h1>
|
|
46
|
+
<p class="message">{{ message }}</p>
|
|
47
|
+
<a class="back" href="{{ back_url }}">Back to sign in</a>
|
|
48
|
+
</div>
|
|
49
|
+
</body>
|
|
50
|
+
</html>
|
|
@@ -9,7 +9,8 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
from django.conf import settings as django_settings
|
|
11
11
|
from django.contrib.auth import login, logout
|
|
12
|
-
from django.http import HttpRequest, HttpResponse,
|
|
12
|
+
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
|
13
|
+
from django.shortcuts import render
|
|
13
14
|
from django.views import View
|
|
14
15
|
|
|
15
16
|
from .client import KeycloakClient, TokenExchangeError
|
|
@@ -24,6 +25,14 @@ SESSION_NEXT_KEY = "pyobs_auth_next"
|
|
|
24
25
|
SESSION_ID_TOKEN_KEY = "pyobs_auth_id_token"
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
def _error_response(request: HttpRequest, message: str) -> HttpResponse:
|
|
29
|
+
"""A styled error page rather than a bare 400 - pyobs_auth doesn't know the host app's own
|
|
30
|
+
login URL name (each service names it differently), so this always links back to "/", which
|
|
31
|
+
every consuming service already bounces an unauthenticated visitor to its own login page
|
|
32
|
+
from."""
|
|
33
|
+
return render(request, "pyobs_auth/error.html", {"message": message, "back_url": "/"}, status=400)
|
|
34
|
+
|
|
35
|
+
|
|
27
36
|
class LoginView(View):
|
|
28
37
|
def get(self, request: HttpRequest) -> HttpResponse:
|
|
29
38
|
settings = get_settings()
|
|
@@ -43,7 +52,7 @@ class CallbackView(View):
|
|
|
43
52
|
def get(self, request: HttpRequest) -> HttpResponse:
|
|
44
53
|
error = request.GET.get("error")
|
|
45
54
|
if error:
|
|
46
|
-
return
|
|
55
|
+
return _error_response(request, f"Keycloak login failed: {error}")
|
|
47
56
|
|
|
48
57
|
code = request.GET.get("code")
|
|
49
58
|
state = request.GET.get("state")
|
|
@@ -52,7 +61,7 @@ class CallbackView(View):
|
|
|
52
61
|
next_url = request.session.pop(SESSION_NEXT_KEY, "/") or "/"
|
|
53
62
|
|
|
54
63
|
if not code or not state or not code_verifier or state != expected_state:
|
|
55
|
-
return
|
|
64
|
+
return _error_response(request, "Invalid or expired login state")
|
|
56
65
|
|
|
57
66
|
settings = get_settings()
|
|
58
67
|
client = KeycloakClient(settings)
|
|
@@ -60,24 +69,24 @@ class CallbackView(View):
|
|
|
60
69
|
try:
|
|
61
70
|
tokens = client.exchange_code(code=code, code_verifier=code_verifier)
|
|
62
71
|
except TokenExchangeError as exc:
|
|
63
|
-
return
|
|
72
|
+
return _error_response(request, f"Token exchange failed: {exc}")
|
|
64
73
|
|
|
65
74
|
access_token = tokens.get("access_token")
|
|
66
75
|
if not access_token:
|
|
67
|
-
return
|
|
76
|
+
return _error_response(request, "No access_token in Keycloak's response")
|
|
68
77
|
|
|
69
78
|
validator = TokenValidator(settings)
|
|
70
79
|
try:
|
|
71
80
|
claims = validator.validate(access_token)
|
|
72
81
|
except TokenValidationError as exc:
|
|
73
|
-
return
|
|
82
|
+
return _error_response(request, f"Received an invalid token: {exc}")
|
|
74
83
|
|
|
75
84
|
user_resolver = settings.resolve_user_callable()
|
|
76
85
|
user = user_resolver(claims)
|
|
77
86
|
if user is None:
|
|
78
|
-
return
|
|
87
|
+
return _error_response(request, "No local user for this token")
|
|
79
88
|
if not user.is_active:
|
|
80
|
-
return
|
|
89
|
+
return _error_response(request, "This account is pending activation. Contact an administrator.")
|
|
81
90
|
|
|
82
91
|
backend = getattr(django_settings, "PYOBS_AUTH_LOGIN_BACKEND", "django.contrib.auth.backends.ModelBackend")
|
|
83
92
|
login(request, user, backend=backend)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyobs-auth"
|
|
3
|
-
version = "2.0.0.
|
|
3
|
+
version = "2.0.0.dev7"
|
|
4
4
|
description = "Shared Keycloak/OIDC authentication client for pyobs web services"
|
|
5
5
|
authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }]
|
|
6
6
|
requires-python = ">=3.11"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|