jbittel / django-mama-cas

A Django Central Authentication Service (CAS) single sign-on server
BSD 3-Clause "New" or "Revised" License
397 stars 112 forks source link

REST authentication #45

Open Jiloc opened 7 years ago

Jiloc commented 7 years ago

Is REST authentication supported or are there any plans to support it?

https://apereo.github.io/cas/5.1.x/installation/Rest-Authentication.html

manelclos commented 6 years ago

Hi @Jiloc, as stated on the webpage linked:

This documentation describes how to delegate and submit authentication requests to a remote REST endpoint. It has nothing to do with the native CAS REST API ...

So this has nothing to do with the CAS Server itself. I mean, the functionality can be created on its own project. Like I'm using REMOTE_USER to authenticate users, so the users are already authenticated when they reach the CAS Server code. I know this is not delegation, but you can easily write a backend that will do what you want.

Hope this helps!

Jiloc commented 6 years ago

Hi @manelclos, Thank you for the suggestion! I actually did it by overriding mama_cas_views.LoginView in my own project. Anyway I still think that it would be very useful feature to introduce in this project.

manelclos commented 6 years ago

@Jiloc are the changes online or can you share them?

Jiloc commented 6 years ago

They are not online, but I will share it here. Anyway I find my adaptation too custom to be used as is.

class LoginRestView(mama_cas_views.LoginView):
    def get_form_kwargs(self):
        """
        Django >= 1.11 supports a request sent to the authenticator
        so we grab that here and pass it along to the form so it can be
        handed off to the authenticators.
        """
        kwargs = super(LoginRestView, self).get_form_kwargs()
        if 'HTTP_AUTHORIZATION' in self.request.META:
            authmeth, auth = self.request.META['HTTP_AUTHORIZATION'].split(' ', 1)
            if authmeth.lower() == 'basic':
                auth = base64.b64decode(auth).decode('utf-8')
                username, password = auth.split(':', 1)
                kwargs['data'] = {
                    'username': username,
                    'password': password
                }
        return kwargs

    def post(self, request, *args, **kwargs):
        return super(LoginRestView, self).post(request, *args, **kwargs)

    @method_decorator(ensure_csrf_cookie)
    def get(self, request, *args, **kwargs):
        """
        (2.1) As a credential requestor, /login accepts three optional
        parameters:
        1. ``service``: the identifier of the application the client is
           accessing. We assume this identifier to be a URL.
        2. ``renew``: requires a client to present credentials
           regardless of any existing single sign-on session.
        3. ``gateway``: causes the client to not be prompted for
           credentials. If a single sign-on session exists the user
           will be logged in and forwarded to the specified service.
           Otherwise, the user remains logged out and is forwarded to
           the specified service.
        """
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        service = request.GET.get('service')
        renew = to_bool(request.GET.get('renew'))
        gateway = to_bool(request.GET.get('gateway'))
        if renew:
            logger.debug("Renew request received by credential requestor")
        elif gateway and service:
            logger.debug("Gateway request received by credential requestor")
            if is_authenticated(request.user):
                st = ServiceTicket.objects.create_ticket(
                    service=service, user=request.user)
                if self.warn_user():
                    return redirect('cas_warn', params={
                        'service': service, 'ticket': st.ticket})
                return redirect(service, params={'ticket': st.ticket})
            else:
                return redirect(service)
        elif is_authenticated(request.user):
            if service:
                logger.debug(
                    "Service ticket request received by credential requestor")
                st = ServiceTicket.objects.create_ticket(
                    service=service, user=request.user)
                if self.warn_user():
                    return redirect('cas_warn', params={
                        'service': service, 'ticket': st.ticket})
                return redirect(service, params={'ticket': st.ticket})
            else:
                return HttpResponse(
                    json.dumps({'message': 'Ok'}),
                    content_type="application/json")
        res = HttpResponse(status=401)
        res['WWW-Authenticate'] = 'Basic'
        return res