Commit f17a87db authored by Lukas Burgey's avatar Lukas Burgey
Browse files

Add upstream endpoints

Relates to #58 and #59
parent 6d8dc023
import logging
import json
import jwt
from urllib.error import HTTPError
from urllib.request import Request, urlopen
from rest_framework.authentication import BaseAuthentication
from . import utils
......@@ -17,18 +15,8 @@ LOGGER = logging.getLogger(__name__)
class OIDCTokenAuthBackend:
def get_userinfo(self, oidc_client, access_token):
req = Request(
oidc_client.provider_info['userinfo_endpoint'],
)
req.add_header('Authorization', access_token)
# execute request
userinfo_bytes = urlopen(req).read()
user_info = json.loads(userinfo_bytes.decode('UTF-8'))
user_info['iss'] = oidc_client.provider_info['issuer']
return user_info
oidc_client.get_userinfo(access_token)
# no issuer -> try all idps :/
def get_userinfo_bruteforce(self, access_token):
......@@ -38,13 +26,11 @@ class OIDCTokenAuthBackend:
oidc_client,
access_token,
)
except HTTPError as exception:
except HTTPError:
pass
raise OIDCConfig.DoesNotExist('Unable to determine IdP')
# raises OIDCConfig.DoesNotExist if no idp can be determined
def get_idp(self, request):
# OPTION 1: issuer set in the 'X-Issuer' header
......
import logging
import re
import json
from django.core.exceptions import ImproperlyConfigured
from django.core.validators import RegexValidator, URLValidator
......@@ -9,6 +10,7 @@ from django.utils.translation import gettext_lazy as _
from django.db import models as db_models
from django_mysql.models import JSONField
from django.conf import settings
from urllib.request import Request, urlopen
from oic.oic import Client
from oic.oic.message import RegistrationResponse
......@@ -127,6 +129,21 @@ class OIDCConfig(db_models.Model):
)
return auth_req.request(self.oidc_client.authorization_endpoint)
# raises HttpError on error
def get_userinfo(self, access_token):
req = Request(
self.provider_info['userinfo_endpoint'],
)
req.add_header('Authorization', access_token)
# execute request
userinfo_bytes = urlopen(req).read()
user_info = json.loads(userinfo_bytes.decode('UTF-8'))
user_info['iss'] = self.provider_info['issuer']
return user_info
def default_idp():
available_idps = OIDCConfig.objects.filter(enabled=True)
......
......@@ -118,7 +118,7 @@ class User(AbstractUser):
return self._is_active
# returns the user as identified by userinfo and idp
# if the user does not exists
# if the user does not exists it is created
@classmethod
def get_user(cls, userinfo, idp):
if 'sub' not in userinfo:
......@@ -136,6 +136,14 @@ class User(AbstractUser):
except cls.DoesNotExist:
return cls.construct_from_userinfo(userinfo, idp)
@classmethod
def update_userinfo(cls, userinfo, idp):
user = cls.objects.get(
idp=idp,
sub=userinfo.get('sub', ''),
)
user.update_userinfo(userinfo)
@classmethod
def construct_from_userinfo(cls, userinfo, idp):
if 'sub' not in userinfo:
......
from django.urls import include, path, re_path
from django.urls import include, path
from django.contrib import admin
from .views.clients import URLPATTERNS as clientapi_urls
from .views.webpage import URLPATTERNS as webpage_urls
from .views.rest import URLPATTERNS as user_rest_urls
from .views.rest import HelpView
from .views.upstream import URLPATTERNS as upstream_urls
from .auth.v1.urls import URLPATTERNS as auth_urls
# all these paths must be configured in the nginx config!
urlpatterns = [
path('client/', include(clientapi_urls)),
path('webpage/', include(webpage_urls)),
path('user/', include(user_rest_urls)),
path('user', HelpView.as_view()),
path('auth/', include(auth_urls)),
path('upstream/', include(upstream_urls)),
path('admin', admin.site.urls),
]
import logging
from urllib.error import HTTPError
from django.urls import path
from django.http import HttpResponse
from rest_framework.views import APIView
from rest_framework.authentication import BasicAuthentication
from feudal.backend.models import User
from feudal.backend.permissions import UpstreamOnly
LOGGER = logging.getLogger(__name__)
# authentication class for the client api
AUTHENTICATION_CLASSES = (BasicAuthentication,)
PERMISSION_CLASSES = (UpstreamOnly,)
class AccessTokenView(APIView):
authentication_classes = AUTHENTICATION_CLASSES
permission_classes = PERMISSION_CLASSES
def put(self, request):
if 'at' not in request.data:
return HttpResponse("Missing field 'at'", status=400)
if request.user.idp is None:
return HttpResponse("No IdP associated", status=500)
at = request.data.get('at')
try:
userinfo = request.user.idp.get_userinfo(at)
User.update_userinfo(userinfo)
return HttpResponse("Updated", status=200)
except HTTPError:
return HttpResponse("Error retrieving userinfo using the access token", status=500)
class UserinfoView(APIView):
authentication_classes = AUTHENTICATION_CLASSES
permission_classes = PERMISSION_CLASSES
def put(self, request):
if 'userinfo' not in request.data:
return HttpResponse("Missing field 'userinfo'", status=400)
if request.user.idp is None:
return HttpResponse("No IdP associated", status=500)
User.update_userinfo(request.data.get('userinfo'), request.user.idp)
return HttpResponse("Updated", status=200)
URLPATTERNS = [
path('at', AccessTokenView().as_view()),
path('userinfo', UserinfoView().as_view()),
]
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment