views.py 5.12 KB
Newer Older
Lukas Burgey's avatar
Lukas Burgey committed
1
2
import json
import logging
3
4
from django.contrib.auth import authenticate, login, logout
from django.shortcuts import redirect
Lukas Burgey's avatar
Lukas Burgey committed
5
from django.views import View
6
from rest_framework import generics, views
Lukas Burgey's avatar
Lukas Burgey committed
7
from rest_framework.permissions import AllowAny
8
from rest_framework.response import Response
Lukas Burgey's avatar
Lukas Burgey committed
9
10
from oic import rndstr
from oic.oic.message import AuthorizationResponse
Lukas Burgey's avatar
Lukas Burgey committed
11

12
from .models import OIDCConfig, default_idp
13
from .serializers import AuthInfoSerializer
Lukas Burgey's avatar
Lukas Burgey committed
14
from . import utils
15

Lukas Burgey's avatar
Lukas Burgey committed
16
17
from ...frontend.views import state_view_data

Lukas Burgey's avatar
Lukas Burgey committed
18
LOGGER = logging.getLogger(__name__)
19

Lukas Burgey's avatar
Lukas Burgey committed
20
IDP_COOKIE_NAME = 'idp_id'
Lukas Burgey's avatar
Lukas Burgey committed
21

22

23
def idp_id_from_request(request):
Lukas Burgey's avatar
Lukas Burgey committed
24
25
26
27
    idp_id = request.COOKIES.get(IDP_COOKIE_NAME, None)
    if idp_id is not None:
        return idp_id

Lukas Burgey's avatar
Lukas Burgey committed
28
    LOGGER.info('idp_id cookie was not set on request %s', request)
Lukas Burgey's avatar
Lukas Burgey committed
29
    return default_idp().id
30
31


32
33
34
35
class AuthException(Exception):
    pass


Lukas Burgey's avatar
Lukas Burgey committed
36
class Auth(View):
Lukas Burgey's avatar
Lukas Burgey committed
37
    def get(self, request):
Lukas Burgey's avatar
Lukas Burgey committed
38
        try:
39
40
41
42
            state = rndstr()
            idp_id = idp_id_from_request(request)
            oidc_config = OIDCConfig.objects.get(id=idp_id)

Lukas Burgey's avatar
Lukas Burgey committed
43
44
            utils.set_session(request, 'state', state)
            utils.set_session(request, 'idp_id', idp_id)
45

Lukas Burgey's avatar
Lukas Burgey committed
46
47
48
49
50
            auth_redirect = oidc_config.get_auth_request(
                oidc_config.oidc_client,
                state,
            )

51
            LOGGER.debug('Auth: redirecting %s to IdP %s', state, oidc_config)
Lukas Burgey's avatar
Lukas Burgey committed
52
            return redirect(auth_redirect)
53

Lukas Burgey's avatar
Lukas Burgey committed
54
        except Exception as exception:
Lukas Burgey's avatar
Lukas Burgey committed
55
56
            LOGGER.error('Auth: %s', exception)

57
58
            # the error is deleted from the session when the state is delivered
            request.session['error'] = 'Server Error'
59
            return redirect('/')
Lukas Burgey's avatar
Lukas Burgey committed
60
61
62


class AuthCallback(View):
Lukas Burgey's avatar
Lukas Burgey committed
63
    def get(self, request):
Lukas Burgey's avatar
Lukas Burgey committed
64
        try:
Lukas Burgey's avatar
Lukas Burgey committed
65
66
            state = utils.get_session(request, 'state', None)
            idp_id = utils.get_session(request, 'idp_id', default_idp().id)
67

68
            oidc_config = OIDCConfig.objects.get(id=idp_id)
69
            oidc_client = oidc_config.oidc_client
70
            LOGGER.debug('AuthCallback: %s returned from IdP %s', state, oidc_config)
71

72
            aresp = oidc_client.parse_response(
73
                AuthorizationResponse,
Lukas Burgey's avatar
Lukas Burgey committed
74
75
                info=json.dumps(request.GET),
            )
76
77
78
            if 'error' in aresp:
                LOGGER.debug('AuthCallback: error response: %s', aresp)
                raise AuthException('Erroneous callback from IdP {}'.format(oidc_config))
79
80

            if not state == aresp['state']:
81
82
                LOGGER.error('AuthCallback: states do not match')
                raise AuthException('AuthCallbackStates do not match')
83
84

            ac_token_response = (
85
86
                oidc_client.do_access_token_request(
                    state=state,
Lukas Burgey's avatar
Lukas Burgey committed
87
88
89
90
91
                    request_args={
                        'code': aresp['code']
                    },
                )
            )
92

93
94
95
96
97
98
99
100
            # FIXME 'email_verified' in user info is no boolean
            # but oic expects it to be
            #user_info = oidc_client.do_user_info_request(
            #    method="GET",
            #    state=state,
            #)
            #LOGGER.debug("EXPERIMENT: %s", user_info)

101
102
103
104
105
106
107
108
109
110
            # 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(
Lukas Burgey's avatar
Lukas Burgey committed
111
112
113
                request,
                token=ac_token_response['access_token'],
            )
114

115
116
            response = redirect('/')

117
            if user is None:
118
                # authentication failed -> "401"
Lukas Burgey's avatar
Lukas Burgey committed
119
                LOGGER.error('User failed to log in')
Lukas Burgey's avatar
Lukas Burgey committed
120
                utils.set_session(request, 'error', 'Login failed')
121
122
123
                # response = HttpResponse('Unauthorized', status=401)
            elif not user.is_active:
                # user is deactivated -> "403"
124
                LOGGER.info('%s tried to log in', user)
Lukas Burgey's avatar
Lukas Burgey committed
125
                utils.set_session(request, 'error', 'Account deactivated')
126
                # response = HttpResponse('Forbidden', status=403)
127
            else:
128
                # user authenticated -> back to frontend
129
                login(request, user)
Lukas Burgey's avatar
Lukas Burgey committed
130
131
                LOGGER.info('AuthCallback: IdP %s authenticated user as %s', oidc_config, user)

132
133
            return response

134
        except AuthException as exception:
Lukas Burgey's avatar
Lukas Burgey committed
135
136
            LOGGER.error('AuthCallback: %s', exception)

137
138
            # the error is deleted from the session when the state is delivered
            request.session['error'] = 'Server Error'
139
            return redirect('/')
Lukas Burgey's avatar
Lukas Burgey committed
140

141
142

class LogoutView(views.APIView):
Lukas Burgey's avatar
Lukas Burgey committed
143
    def post(self, request):
144
        LOGGER.debug('logged out %s', request.user)
145
        logout(request)
Lukas Burgey's avatar
Lukas Burgey committed
146
        return Response(state_view_data(request))
147
148
149
150
151
152
153
154
155


class AuthInfo(generics.RetrieveAPIView):
    permission_classes = (AllowAny,)
    serializer_class = AuthInfoSerializer

    def get_object(self):
        idps = OIDCConfig.objects.filter(enabled=True)
        return {
Lukas Burgey's avatar
Lukas Burgey committed
156
157
158
            'idps': idps,
            'default': idps.first().id,
        }