Skip to content

Commit

Permalink
Hub: Log Token Claims
Browse files Browse the repository at this point in the history
Log token claims once per connection, to identify users.
  • Loading branch information
holesch committed Sep 15, 2024
1 parent 296d2fb commit 551bf1e
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 14 deletions.
35 changes: 29 additions & 6 deletions doc/reference/hub-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,31 @@ configuration at `<value>/.well-known/openid-configuration`.

The client ID of `not-my-board`. Get this value from the OpenID provider.

### `auth.show_claims`
### `auth.issuers`

**Type:** Table \
**Required:** No

Contains extra configuration per OpenID provider.

### `auth.issuers.<issuer_url>`

**Type:** Table \
**Required:** No

Contains configuration for the OpenID provider with the URL matching
`<issuer_url>`.

### `auth.issuers.<issuer_url>.show_claims`

**Type:** Array of strings \
**Required:** No

Allows the administrator to filter the shown claims when users log in. Specify
the claims an administrator might need to give the user permissions. If the
option is not set, then all claims are shown. If it's set to an empty array,
then no claims are shown.
Allows the administrator to filter the shown claims of the OpenID Connect ID
token. The filtered claims are logged by the *Hub* and are shown to the users,
when they log in. Specify the claims an administrator might need to give the
user permissions. If the option is not set, then all claims are shown. If it's
set to an empty array, then no claims are shown.

### `auth.permissions`

Expand Down Expand Up @@ -100,6 +116,8 @@ log_level = "info"
[auth]
issuer = "http://keycloak.example.com/realms/master"
client_id = "not-my-board"
[auth.issuers."http://keycloak.example.com/realms/master"]
show_claims = ["sub", "preferred_username"]
[[auth.permissions]]
Expand All @@ -119,7 +137,12 @@ log_level = "info"
[auth]
issuer = "https://login.microsoftonline.com/common/v2.0"
client_id = "11111111-2222-1111-2222-000000000000"
show_claims = ["oid", "iss", "preferred_username"]
[auth.issuers."https://login.microsoftonline.com/common/v2.0"]
show_claims = ["preferred_username", "oid", "iss"]
[auth.issuers."https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0"]
show_claims = ["preferred_username", "oid", "iss"]
[[auth.permissions]]
claims.oid = "11111111-2222-1111-2222-333333333333"
Expand Down
52 changes: 44 additions & 8 deletions not_my_board/_hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import pathlib
import random
from dataclasses import dataclass
from typing import Dict, Union
from typing import Dict, List, Optional, Union

import asgineer

Expand Down Expand Up @@ -165,17 +165,26 @@ def __init__(self, config=None, http_client=None):

auth_config = config.get("auth")
if auth_config:
required_keys = {"issuer", "client_id"}
optional_keys = {"show_claims"}
keys = required_keys | (optional_keys & auth_config.keys())
self._auth_info = {k: auth_config[k] for k in keys}

trusted_issuers = {auth_config["issuer"]}
for permission in auth_config["permissions"]:
issuer = permission["claims"].get("iss")
if issuer:
trusted_issuers.add(issuer)

def make_issuer_config(issuer):
issuer_config = auth_config.get("issuers", {}).get(issuer, {})
return IssuerConfig(**issuer_config)

self._issuer_configs = {
issuer: make_issuer_config(issuer) for issuer in trusted_issuers
}

keys = {"issuer", "client_id"}
self._auth_info = {k: auth_config[k] for k in keys}
issuer_config = self._issuer_configs[auth_config["issuer"]]
if issuer_config.show_claims is not None:
self._auth_info["show_claims"] = issuer_config.show_claims

def make_permission(d):
d["claims"].setdefault("iss", auth_config["issuer"])
return Permission.from_dict(d)
Expand All @@ -189,6 +198,7 @@ def make_permission(d):
self._auth_info = {}
self._permissions = []
self._validator = None
self._issuer_configs = {}

self._id_generator = itertools.count(start=1)

Expand Down Expand Up @@ -216,7 +226,10 @@ async def _connection_context(self, channel):
id_ = next(self._id_generator)
connection_id_var.set(id_)
self._reservations[id_] = set()
authenticator = Authenticator(self._permissions, self._validator, channel)
authenticator = Authenticator(
self._permissions, self._validator, channel, self._issuer_configs
)

authenticator_var.set(authenticator)

try:
Expand Down Expand Up @@ -343,15 +356,17 @@ class Authenticator(util.ContextStack):
_leeway = datetime.timedelta(seconds=30)
_timeout = datetime.timedelta(seconds=30)

def __init__(self, permissions, validator, channel):
def __init__(self, permissions, validator, channel, issuer_configs):
self._permissions = permissions
self._validator = validator
self._channel = channel
self._issuer_configs = issuer_configs
self._required_roles = set()
self._refresh_start_event = asyncio.Event()
self._roles = None
self._expires = None
self._roles_lock = asyncio.Lock()
self._previous_claims = None

async def _context_stack(self, stack):
if self._validator:
Expand Down Expand Up @@ -386,6 +401,22 @@ async def _request_roles(self):
)
roles = set()

if logger.isEnabledFor(logging.INFO):
show_claims = self._issuer_configs[token_claims["iss"]].show_claims
if show_claims is not None:
filtered_claims = [
(c, token_claims[c]) for c in show_claims if c in token_claims
]
else:
filtered_claims = list(token_claims.items())

if filtered_claims and filtered_claims != self._previous_claims:
claims_str = ", ".join(
[f"{k!r}: {v!r}" for k, v in filtered_claims]
)
logger.info("Token claims: %s", claims_str)
self._previous_claims = filtered_claims

for permission in self._permissions:
if permission.roles <= roles:
# permission rule has no new roles, skip
Expand Down Expand Up @@ -452,6 +483,11 @@ def from_dict(cls, d):
return cls(claims, set(d["roles"]))


@dataclass
class IssuerConfig:
show_claims: Optional[List[str]] = None


def _unmap_ip(ip_str):
"""Resolve IPv4-mapped-on-IPv6 to an IPv4 address"""
ip = ipaddress.ip_address(ip_str)
Expand Down

0 comments on commit 551bf1e

Please sign in to comment.