From 9df1cd2e31ef44e36dbb3b3bcf348fecff615572 Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Tue, 17 Nov 2015 14:50:16 +0100 Subject: [PATCH] Add javascript login function allow service A to log user to service B via javascript CORS need to be correctly configured if not this can lead to security issues. Please do not put Access-Control-Allow-Origin: "*". You can use django-cors-headers to properly configure CORS --- cas_server/static/cas_server/cas.js | 53 ++++++++++++++ cas_server/utils.py | 18 ++++- cas_server/views.py | 103 +++++++++++++++++++++------- 3 files changed, 149 insertions(+), 25 deletions(-) create mode 100644 cas_server/static/cas_server/cas.js diff --git a/cas_server/static/cas_server/cas.js b/cas_server/static/cas_server/cas.js new file mode 100644 index 0000000..6b1159c --- /dev/null +++ b/cas_server/static/cas_server/cas.js @@ -0,0 +1,53 @@ +function cas_login(cas_server_login, service, login_service){ + url = cas_server_login + '?service=' + encodeURIComponent(service); + $.ajax({ + type: 'GET', + url:url, + beforeSend: function (request) { + request.setRequestHeader("X-AJAX", "1"); + }, + xhrFields: { + withCredentials: true + }, + success: function(data, textStatus, request){ + if(data.status == 'success'){ + $.ajax({ + type: 'GET', + url: data.url, + xhrFields: { + withCredentials: true + }, + }); + } else { + if(data.detail == "login required"){ + window.location.href = cas_server_login + '?service=' + encodeURIComponent(login_service); + } else { + alert('error: ' + data.messages[1].message); + } + } + }, + error: function (request, textStatus, errorThrown) {}, + }); +} + +function cas_logout(cas_server_logout){ + $.ajax({ + type: 'GET', + url:cas_server_logout, + beforeSend: function (request) { + request.setRequestHeader("X-AJAX", "1"); + }, + xhrFields: { + withCredentials: true + }, + error: function (request, textStatus, errorThrown) {}, + success: function(data, textStatus, request){ + if(data.status == 'error'){ + alert('error: ' + data.messages[1].message); + } + }, + }); +} + + + diff --git a/cas_server/utils.py b/cas_server/utils.py index f2b94ad..a4ae87f 100644 --- a/cas_server/utils.py +++ b/cas_server/utils.py @@ -14,11 +14,12 @@ from .default_settings import settings from django.utils.importlib import import_module from django.core.urlresolvers import reverse -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, HttpResponse +from django.contrib import messages import random import string - +import json try: from urlparse import urlparse, urlunparse, parse_qsl @@ -27,6 +28,13 @@ except ImportError: from urllib.parse import urlparse, urlunparse, parse_qsl, urlencode +def JsonResponse(request, data): + data["messages"] = [] + for msg in messages.get_messages(request): + data["messages"].append({'message': msg.message, 'level': msg.level_tag}) + return HttpResponse(json.dumps(data), content_type="application/json") + + def import_attr(path): """transform a python module.attr path to the attr""" if not isinstance(path, str): @@ -42,6 +50,12 @@ def redirect_params(url_name, params=None): return HttpResponseRedirect(url + "?%s" % params) +def reverse_params(url_name, params=None, **kwargs): + url = reverse(url_name, **kwargs) + params = urlencode(params if params else {}) + return url + "?%s" % params + + def update_url(url, params): """update params in the `url` query string""" if not isinstance(url, bytes): diff --git a/cas_server/views.py b/cas_server/views.py index f5494ce..5316ebc 100644 --- a/cas_server/views.py +++ b/cas_server/views.py @@ -13,6 +13,7 @@ from .default_settings import settings from django.shortcuts import render, redirect +from django.core.urlresolvers import reverse from django.http import HttpResponse, HttpResponseRedirect from django.contrib import messages from django.utils.decorators import method_decorator @@ -30,6 +31,7 @@ import cas_server.utils as utils import cas_server.forms as forms import cas_server.models as models +from utils import JsonResponse from .models import ServiceTicket, ProxyTicket, ProxyGrantingTicket from .models import ServicePattern @@ -93,6 +95,7 @@ class LogoutView(View, LogoutMixin): self.request = request self.service = request.GET.get('service') self.url = request.GET.get('url') + self.ajax = 'HTTP_X_AJAX' in request.META def get(self, request, *args, **kwargs): """methode called on GET request on this view""" @@ -108,11 +111,19 @@ class LogoutView(View, LogoutMixin): # else redirect to login page else: if settings.CAS_REDIRECT_TO_LOGIN_AFTER_LOGOUT: - messages.add_message(request, messages.SUCCESS, _(u'Successfully logout')) - return redirect("cas_server:login") + if self.ajax: + url = reverse("cas_server:login") + data = {'status': 'success', 'detail': 'logout', 'url': url} + return JsonResponse(request, data) + else: + return redirect("cas_server:login") else: - return render(request, settings.CAS_LOGOUT_TEMPLATE) + if self.ajax: + data = {'status': 'success', 'detail': 'logout'} + return JsonResponse(request, data) + else: + return render(request, settings.CAS_LOGOUT_TEMPLATE) class LoginView(View, LogoutMixin): @@ -129,6 +140,7 @@ class LoginView(View, LogoutMixin): renew = None gateway = None method = None + ajax = None renewed = False warned = False @@ -146,6 +158,7 @@ class LoginView(View, LogoutMixin): self.renew = True if request.POST.get('renew') else False self.gateway = request.POST.get('gateway') self.method = request.POST.get('method') + self.ajax = 'HTTP_X_AJAX' in request.META def check_lt(self): # save LT for later check @@ -223,6 +236,7 @@ class LoginView(View, LogoutMixin): self.renew = True if request.GET.get('renew') else False self.gateway = request.GET.get('gateway') self.method = request.GET.get('method') + self.ajax = 'HTTP_X_AJAX' in request.META def get(self, request, *args, **kwargs): """methode called on GET request on this view""" @@ -265,40 +279,55 @@ class LoginView(View, LogoutMixin): _(u"Authentication has been required by service %(name)s (%(url)s)") % {'name': service_pattern.name, 'url': self.service} ) - return render( - self.request, - settings.CAS_WARN_TEMPLATE, - {'service_ticket_url': self.user.get_service_url( - self.service, - service_pattern, - renew=self.renew - )} + if self.ajax: + data = {"status": "error", "detail": "confirmation needed"} + return JsonResponse(request, data) + else: + return render( + self.request, + settings.CAS_WARN_TEMPLATE, + {'service_ticket_url': self.user.get_service_url( + self.service, + service_pattern, + renew=self.renew + )} ) else: # redirect, using method ? list(messages.get_messages(self.request)) # clean messages before leaving django - return HttpResponseRedirect( - self.user.get_service_url(self.service, service_pattern, renew=self.renew) + redirect_url = self.user.get_service_url( + self.service, + service_pattern, + renew=self.renew ) + if not self.ajax: + return HttpResponseRedirect(redirect_url) + else: + data = {"status": "success", "detail": "auth", "url": redirect_url} + return JsonResponse(self.request, data) except ServicePattern.DoesNotExist: + error = 1 messages.add_message( self.request, messages.ERROR, _(u'Service %(url)s non allowed.') % {'url': self.service} ) except models.BadUsername: + error = 2 messages.add_message( self.request, messages.ERROR, _(u"Username non allowed") ) except models.BadFilter: + error = 3 messages.add_message( self.request, messages.ERROR, _(u"User charateristics non allowed") ) except models.UserFieldNotDefined: + error = 4 messages.add_message( self.request, messages.ERROR, @@ -307,11 +336,19 @@ class LoginView(View, LogoutMixin): ) # if gateway is set and auth failed redirect to the service without authentication - if self.gateway: + if self.gateway and not self.ajax: list(messages.get_messages(self.request)) # clean messages before leaving django return HttpResponseRedirect(self.service) - return render(self.request, settings.CAS_LOGGED_TEMPLATE, {'session': self.request.session}) + if not self.ajax: + return render( + self.request, + settings.CAS_LOGGED_TEMPLATE, + {'session': self.request.session} + ) + else: + data = {"status": "error", "detail": "auth", "code": error} + return JsonResponse(self.request, data) def authenticated(self): """Processing authenticated users""" @@ -322,24 +359,36 @@ class LoginView(View, LogoutMixin): ) except models.User.DoesNotExist: self.logout() - return utils.redirect_params("cas_server:login", params=self.request.GET) + if self.ajax: + data = { + "status": "error", + "detail": "login required", + "url": utils.reverse_params("cas_server:login", params=self.request.GET) + } + return JsonResponse(self.request, data) + else: + return utils.redirect_params("cas_server:login", params=self.request.GET) # if login agains a service is self.requestest if self.service: return self.service_login() else: - return render( - self.request, - settings.CAS_LOGGED_TEMPLATE, - {'session': self.request.session} - ) + if self.ajax: + data = {"status": "success", "detail": "logged"} + return JsonResponse(self.request, data) + else: + return render( + self.request, + settings.CAS_LOGGED_TEMPLATE, + {'session': self.request.session} + ) def not_authenticated(self): """Processing non authenticated users""" if self.service: try: service_pattern = ServicePattern.validate(self.service) - if self.gateway: + if self.gateway and not self.ajax: # clean messages before leaving django list(messages.get_messages(self.request)) return HttpResponseRedirect(self.service) @@ -363,7 +412,15 @@ class LoginView(View, LogoutMixin): messages.ERROR, _(u'Service %s non allowed') % self.service ) - return render(self.request, settings.CAS_LOGIN_TEMPLATE, {'form': self.form}) + if self.ajax: + data = { + "status": "error", + "detail": "login required", + "url": utils.reverse_params("cas_server:login", params=self.request.GET) + } + return JsonResponse(self.request, data) + else: + return render(self.request, settings.CAS_LOGIN_TEMPLATE, {'form': self.form}) def common(self): """Part execute uppon GET and POST request"""