providers/oauth2: handler PropertyMapping exceptions and create event

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-07-21 22:51:39 +02:00
parent b3da94bbb8
commit a7467e6740
2 changed files with 127 additions and 6 deletions

View File

@ -0,0 +1,111 @@
"""Test userinfo view"""
import json
from dataclasses import asdict
from django.urls import reverse
from django.utils.encoding import force_str
from authentik.core.models import Application, User
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.flows.models import Flow
from authentik.managed.manager import ObjectManager
from authentik.providers.oauth2.generators import (
generate_client_id,
generate_client_secret,
)
from authentik.providers.oauth2.models import (
IDToken,
OAuth2Provider,
RefreshToken,
ScopeMapping,
)
from authentik.providers.oauth2.tests.utils import OAuthTestCase
class TestUserinfo(OAuthTestCase):
"""Test token view"""
def setUp(self) -> None:
super().setUp()
ObjectManager().run()
self.app = Application.objects.create(name="test", slug="test")
self.provider: OAuth2Provider = OAuth2Provider.objects.create(
name="test",
client_id=generate_client_id(),
client_secret=generate_client_secret(),
authorization_flow=Flow.objects.first(),
redirect_uris="",
rsa_key=CertificateKeyPair.objects.first(),
)
self.provider.property_mappings.set(ScopeMapping.objects.all())
# Needs to be assigned to an application for iss to be set
self.app.provider = self.provider
self.app.save()
self.user = User.objects.get(username="akadmin")
self.token: RefreshToken = RefreshToken.objects.create(
provider=self.provider,
user=self.user,
access_token=generate_client_id(),
refresh_token=generate_client_id(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
IDToken("foo", "bar"),
)
),
)
def test_userinfo_normal(self):
"""test user info with all normal scopes"""
res = self.client.get(
reverse("authentik_providers_oauth2:userinfo"),
HTTP_AUTHORIZATION=f"Bearer {self.token.access_token}",
)
self.assertJSONEqual(
force_str(res.content),
{
"name": "authentik Default Admin",
"given_name": "authentik Default Admin",
"family_name": "",
"preferred_username": "akadmin",
"nickname": "akadmin",
"groups": ["authentik Admins"],
"sub": "bar",
},
)
self.assertEqual(res.status_code, 200)
def test_userinfo_invalid_scope(self):
"""test user info with a broken scope"""
scope = ScopeMapping.objects.create(
name="test", scope_name="openid", expression="q"
)
self.provider.property_mappings.add(scope)
res = self.client.get(
reverse("authentik_providers_oauth2:userinfo"),
HTTP_AUTHORIZATION=f"Bearer {self.token.access_token}",
)
self.assertJSONEqual(
force_str(res.content),
{
"name": "authentik Default Admin",
"given_name": "authentik Default Admin",
"family_name": "",
"preferred_username": "akadmin",
"nickname": "akadmin",
"groups": ["authentik Admins"],
"sub": "bar",
},
)
self.assertEqual(res.status_code, 200)
events = Event.objects.filter(
action=EventAction.CONFIGURATION_ERROR,
)
self.assertTrue(events.exists())
self.assertEqual(
events.first().context["message"],
"Failed to evaluate property-mapping: name 'q' is not defined",
)

View File

@ -7,6 +7,8 @@ from django.http.response import HttpResponseBadRequest
from django.views import View
from structlog.stdlib import get_logger
from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.events.models import Event, EventAction
from authentik.providers.oauth2.constants import (
SCOPE_GITHUB_ORG_READ,
SCOPE_GITHUB_USER,
@ -63,12 +65,20 @@ class UserInfoView(View):
for scope in ScopeMapping.objects.filter(
provider=token.provider, scope_name__in=scopes_from_client
).order_by("scope_name"):
value = None
try:
value = scope.evaluate(
user=token.user,
request=self.request,
provider=token.provider,
token=token,
)
except PropertyMappingExpressionException as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to evaluate property-mapping: {str(exc)}",
mapping=scope,
).from_http(self.request)
if value is None:
continue
if not isinstance(value, dict):