Commit c7cd871a authored by Lukas Burgey's avatar Lukas Burgey

Rework error reporting of authentication process

parent c3cd1719
from ... import models
from django.db.utils import OperationalError
from django.core.exceptions import ObjectDoesNotExist
from django.db import models as db_models
from oic.oic import Client
from oic.utils.authn.client import CLIENT_AUTHN_METHOD
from oic.oic.message import RegistrationResponse
from oic.utils.authn.client import CLIENT_AUTHN_METHOD
from urllib.request import Request, urlopen
from ... import models
from django.core.exceptions import ObjectDoesNotExist
from django.conf import settings
import json
import logging
logger = logging.getLogger(__name__)
oidc_client = {}
......@@ -60,15 +63,20 @@ class OIDCConfig(db_models.Model):
return self.name
def get_oidc_client():
# TODO dubious
oidc_config = OIDCConfig.objects.all()[settings.OIDC_CONFIG_INDEX]
return oidc_config.oidc_client
def default_idp():
return OIDCConfig.objects.filter(enabled=True).first()
class OIDCTokenAuthBackend(object):
def get_user_info(self, access_token, token_type='Bearer'):
q = Request(get_oidc_client().provider_info['userinfo_endpoint'])
def get_user_info(self, request, access_token, token_type='Bearer'):
try:
idp_id = request.session['idp_id']
except OperationalError:
logger.error('OperationalError: Unable to get from session')
raise
oidc_config = OIDCConfig.objects.get(id=idp_id)
q = Request(oidc_config.oidc_client.provider_info['userinfo_endpoint'])
auth = (token_type + ' ' + access_token)
q.add_header('Authorization', auth)
......@@ -79,7 +87,7 @@ class OIDCTokenAuthBackend(object):
if token is None:
return None
user_info = self.get_user_info(token)
user_info = self.get_user_info(request, token)
try:
user = models.User.objects.get(
sub=user_info['sub'])
......
from django.conf.urls import url
from . import views
from ...frontend import views as frontend_views
urlpatterns = [
url(r'^info/', views.AuthInfo.as_view()),
url(r'^request/', views.Auth.as_view()),
url(r'^callback/', views.AuthCallback.as_view()),
url(r'^logout/', frontend_views.LogoutView.as_view()),
url(r'^logout/', views.LogoutView.as_view()),
]
from django.contrib.auth import authenticate, login
from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponse, HttpResponseServerError
from django.db.utils import OperationalError
from django.shortcuts import redirect, render
from django.shortcuts import redirect
from django.views import View
from oic import rndstr
from oic.oic.message import AuthorizationResponse
from rest_framework.permissions import AllowAny
from rest_framework import generics
from rest_framework import generics, views
from rest_framework.response import Response
import json
from .models import OIDCConfig
from .models import OIDCConfig, default_idp
from .serializers import AuthInfoSerializer
import logging
logger = logging.getLogger(__name__)
def oidc_config_for_request(request):
idp_id = request.GET.get('idp', None)
if idp_id is not None:
try:
return OIDCConfig.objects.get(id=idp_id)
except Exception:
print('No OIDCConfig with id {}'.format(idp_id))
raise
def idp_id_from_request(request):
id = request.COOKIES.get('idp_id', None)
if id is not None:
return id
else:
return OIDCConfig.objects.first()
return default_idp().id
class AuthInfo(generics.RetrieveAPIView):
permission_classes = (AllowAny,)
serializer_class = AuthInfoSerializer
def get_session(request, key, default):
try:
value = request.session.get(key, None)
except OperationalError:
logger.error('Operational: Error getting from session')
raise
if value is not None:
return value
else:
return default
def get_object(self):
idps = OIDCConfig.objects.filter(enabled=True)
return {
'idps': idps,
'default': idps.first().id,
}
def set_session(request, key, value):
try:
value = request.session[key] = value
except OperationalError:
logger.error('Operational: Error setting in session')
raise
class Auth(View):
def get(self, request, **kwargs):
state = rndstr()
try:
request.session['state'] = state
# request.session['nonce'] = rndstr()
except OperationalError:
return redirect('/')
oidc_config = oidc_config_for_request(request)
try:
return redirect(oidc_config.get_auth_request(
oidc_config.oidc_client,
state))
except Exception:
print('Malformed oidc config: {}'.format(oidc_config))
state = rndstr()
idp_id = idp_id_from_request(request)
oidc_config = OIDCConfig.objects.get(id=idp_id)
set_session(request, 'state', state)
set_session(request, 'idp_id', idp_id)
try:
response = redirect(oidc_config.get_auth_request(
oidc_config.oidc_client,
state))
return response
except Exception:
logger.error('Malformed oidc config: {}'.format(oidc_config))
raise
except Exception as e:
logger.error('request: {}'.format(e))
# the error is deleted from the session when the state is delivered
request.session['error'] = 'Server Error'
return redirect('/')
class AuthCallback(View):
def get(self, request, **kwargs):
try:
state = request.session['state']
except OperationalError:
state = get_session(request, 'state', None)
idp_id = get_session(request, 'idp_id', default_idp().id)
oidc_config = OIDCConfig.objects.get(id=idp_id)
aresp = oidc_config.oidc_client.parse_response(
AuthorizationResponse,
info=json.dumps(request.GET))
if not state == aresp['state']:
logger.error('states do not match')
raise Exception('States do not match')
ac_token_response = (
oidc_config.oidc_client.do_access_token_request(
state=aresp['state'],
request_args={
'code': aresp['code']
},
)
)
# does fail with 'invalid_token'
# user_info = OIDC_CLIENT.do_user_info_request(
# statearesp['state'])
# user_info = self.get_user_info(ac_token_response['access_token'])
# try:
# u = models.User.objects.get(sub=user_info['sub'])
# except:
# u = models.user_from_user_info(user_info)
# u.save()
# response = render(request, 'backend/index.html', context)
user = authenticate(
request,
token=ac_token_response['access_token'])
if user is None:
# authentication failed -> 401
logger.error('Login for a user failed')
response = HttpResponse('Unauthorized', status=401)
else:
# redirect back to the frontend
login(request, user)
response = redirect('/')
response.set_cookie('sessionid', request.COOKIES['sessionid'])
return response
except Exception as e:
logger.error('request: {}'.format(e))
# the error is deleted from the session when the state is delivered
request.session['error'] = 'Server Error'
return redirect('/')
oidc_config = OIDCConfig.objects.all()[0]
oidc_client = oidc_config.oidc_client
aresp = oidc_client.parse_response(
AuthorizationResponse,
info=json.dumps(request.GET))
assert state == aresp['state']
code = aresp['code']
args = {
'code': code,
}
ac_token_response = oidc_client.do_access_token_request(
state=aresp['state'],
request_args=args)
# does fail with 'invalid_token'
# user_info = OIDC_CLIENT.do_user_info_request(
# statearesp['state'])
# user_info = self.get_user_info(ac_token_response['access_token'])
# try:
# u = models.User.objects.get(sub=user_info['sub'])
# except:
# u = models.user_from_user_info(user_info)
# u.save()
# response = render(request, 'backend/index.html', context)
user = authenticate(request, token=ac_token_response['access_token'])
if user is None:
# authentication failed -> 401
response = render(request, 'backend/index.html', status=401)
else:
# redirect back to the frontend
login(request, user)
response = redirect('/')
response.set_cookie('sessionid', request.COOKIES['sessionid'])
return response
class LogoutView(views.APIView):
def post(self, request, format=None):
logout(request)
return Response({})
class AuthInfo(generics.RetrieveAPIView):
permission_classes = (AllowAny,)
serializer_class = AuthInfoSerializer
def get_object(self):
idps = OIDCConfig.objects.filter(enabled=True)
return {
'idps': idps,
'default': idps.first().id,
}
from django.contrib.auth import logout
from django.db import connections
from django.db.utils import OperationalError
from django.shortcuts import get_object_or_404
from rest_framework import views, viewsets
from rest_framework.permissions import AllowAny
from rest_framework.authentication import TokenAuthentication
from rest_framework.response import Response
from rest_framework import status
......@@ -23,6 +21,11 @@ def state_response(request):
response['state'] = serializers.StateSerializer(
frontend_models.State(request.user)).data
if 'error' in request.session:
response['error'] = request.session['error']
# we display errors only once
del request.session['error']
return Response(response)
......@@ -44,12 +47,6 @@ class OperationalView(views.APIView):
return Response(response)
class LogoutView(views.APIView):
def post(self, request, format=None):
logout(request)
return state_response(request)
class StateView(views.APIView):
permission_classes = (AllowAny,)
......
......@@ -21,10 +21,6 @@ SESSION_COOKIE_AGE = 3600
CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN'
# index in the django_backend.backend.auth.models.OIDCConfig table
OIDC_CONFIG_INDEX = 0
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
......
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