Skip to content

Commit 39f0962

Browse files
committed
Add application fetching from Keycloak
1 parent 4d7ab4a commit 39f0962

File tree

5 files changed

+181
-22
lines changed

5 files changed

+181
-22
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dependencies = [
1414
"pydantic-settings>=2.6.1",
1515
"waitress>=3.0.1",
1616
"authentik-client>=2024.10.4.post1733219872",
17+
"mantelo>=2.1.1",
1718
]
1819

1920
[build-system]

src/blobdash/__init__.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
from .settings import Settings
7-
from .auth import AuthentikAuthProvider
7+
from .auth import AuthentikAuthProvider, KeycloakAuthProvider
88
from .applications import ApplicationProvider
99

1010

@@ -21,14 +21,21 @@ def create_app():
2121
secho(e)
2222
raise SystemExit(1)
2323

24-
if settings.auth.apps.enabled:
25-
match settings.auth.apps.provider:
26-
case "authentik":
27-
auth_provider = AuthentikAuthProvider(
28-
settings.auth.apps.host, settings.auth.apps.token
29-
)
30-
else:
31-
auth_provider = None
24+
match settings.auth.fetch.provider:
25+
case "authentik":
26+
auth_provider = AuthentikAuthProvider(
27+
settings.auth.fetch.host, settings.auth.fetch.token
28+
)
29+
case "keycloak":
30+
auth_provider = KeycloakAuthProvider(
31+
settings.auth.fetch.host,
32+
settings.auth.fetch.realm,
33+
settings.auth.fetch.auth_realm,
34+
settings.auth.fetch.id,
35+
settings.auth.fetch.secret,
36+
)
37+
case None:
38+
auth_provider = None
3239

3340
app_provider = ApplicationProvider(auth_provider, settings.apps)
3441

src/blobdash/auth.py

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,64 @@
11
import json
22
import urllib.parse
33
from abc import ABC, abstractmethod
4+
45
import authentik_client
6+
from mantelo import KeycloakAdmin
7+
58

69
from .applications import Application
710

811

912
class AuthProvider(ABC):
10-
def __init__(self, host: str, token: str) -> None:
11-
self.host = host
12-
self.token = token
13-
1413
@abstractmethod
1514
def get_applications(self, username):
1615
pass
1716

1817

18+
class KeycloakAuthProvider(AuthProvider):
19+
def __init__(self, host, realm, auth_realm, id, secret):
20+
self.host = host
21+
self.realm = realm
22+
self.auth_realm = realm if auth_realm is None else auth_realm
23+
self.id = id
24+
self.secret = secret
25+
26+
self._api = KeycloakAdmin.from_client_credentials(
27+
server_url=self.host,
28+
realm_name=self.realm,
29+
authentication_realm_name=self.auth_realm,
30+
client_id=self.id,
31+
client_secret=self.secret,
32+
)
33+
34+
def get_applications(self, username):
35+
# Get user ID from username. Returns a list of user objects, so choose the first (and only)
36+
# entry and grab its ID.
37+
user = self._api.users.get(username=username, exact=True)[0]["id"]
38+
39+
# Get a list of all clients the user has logged into, by cycling through consents for the
40+
# user ID and grabbing the client object associated with that client ID
41+
clients = [
42+
self._api.clients.get(clientId=consent["clientId"])[0]
43+
for consent in self._api.users(user).consents.get()
44+
]
45+
46+
return {
47+
client["clientId"]: Application(
48+
name=client["name"],
49+
url=client["baseUrl"],
50+
icon=client["attributes"]["logoUri"],
51+
desc=client["description"],
52+
)
53+
for client in clients
54+
}
55+
56+
1957
class AuthentikAuthProvider(AuthProvider):
2058
def __init__(self, host, token):
2159
# Base path for Authentik API
22-
host = urllib.parse.urljoin(host, "/api/v3")
23-
24-
super().__init__(host, token)
60+
self.host = urllib.parse.urljoin(host, "/api/v3")
61+
self.token = token
2562

2663
# Set up API client. The `CoreAPI` is the only one we need to view users and applications
2764
self._api = authentik_client.CoreApi(

src/blobdash/settings.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,27 @@
1212
from .applications import Application
1313

1414

15-
class AuthApplicationSettings(BaseModel):
16-
enabled: bool = False
17-
provider: Literal["authentik"] = "authentik"
18-
host: str = "https://auth.example.com"
19-
token: str = "changeme"
15+
class AuthentikSettings(BaseModel):
16+
provider: Literal["authentik"]
17+
host: str
18+
token: str
19+
20+
21+
class KeycloakSettings(BaseModel):
22+
provider: Literal["keycloak"]
23+
host: str
24+
auth_realm: Optional[str] = None
25+
realm: str
26+
id: str
27+
secret: str
2028

2129

2230
class AuthSettings(BaseModel):
2331
enabled: bool = False
2432
header: str = "X-App-User"
2533
logout_url: str = "/flows/-/default/invalidation"
2634
default_user: Optional[str] = None
27-
apps: AuthApplicationSettings = AuthApplicationSettings()
35+
fetch: AuthentikSettings | KeycloakSettings | None = None
2836

2937

3038
class DashdotSettings(BaseModel):

uv.lock

Lines changed: 106 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)