from django.conf import settings as django_settings from django.contrib import messages from django.contrib.auth import authenticate, login from django.contrib.auth.decorators import permission_required from django.contrib.auth.models import Group, User from django.contrib.auth.views import redirect_to_login from django.contrib.messages.views import SuccessMessageMixin from django.db.models import Prefetch from django.http import QueryDict from django.shortcuts import redirect, render from django.urls import reverse, reverse_lazy from django.utils.decorators import method_decorator from django.utils.translation import ugettext_lazy as _ from django.views.decorators.http import require_http_methods from django.views.generic import View from django.views.generic.edit import CreateView, UpdateView from .forms import GroupForm from .models import GenericTeamToken class GenericLoginView(View): """ View to authenticate as kfet generic user. It is a 2-step view. First, issue a token if user is a team member and send him to the logout view (for proper disconnect) with callback url to here. Then authenticate the token to log in as the kfet generic user. Token is stored in COOKIES to avoid share it with the authentication provider, which can be external. Session is unusable as it will be cleared on logout. """ TOKEN_COOKIE_NAME = "kfettoken" @method_decorator(require_http_methods(["GET", "POST"])) def dispatch(self, request, *args, **kwargs): token = request.get_signed_cookie(self.TOKEN_COOKIE_NAME, None) if not token: if not request.user.has_perm("kfet.is_team"): return redirect_to_login(request.get_full_path()) if request.method == "POST": # Step 1: set token and logout user. return self.prepare_auth(request) else: # GET request should not change server/client states. Send a # confirmation template to emit a POST request. return render( request, "kfet/confirm_form.html", { "title": _("Ouvrir une session partagée"), "text": _( "Êtes-vous sûr·e de vouloir ouvrir une session " "partagée ?" ), }, ) else: # Step 2: validate token. return self.validate_auth(token) def prepare_auth(self, request): # Issue token. token = GenericTeamToken.objects.create_token() # When CAS logs the user out, the generic login has to be called back. # The corresponding callback URL is provided as a GET parameter. # The renaming of the CAS logout "url" parameter to "service" is being forced, # which is why the CAS logout URL with callback is constructed ad hoc, # without relying on Django redirection to Django CAS. generic_login_url = request.build_absolute_uri() # preserves next parameter generic_login_qd = QueryDict(mutable=True) generic_login_qd["service"] = generic_login_url cas_server_url = django_settings.CAS_SERVER_URL cas_logout_url = cas_server_url + "logout" cas_callback_url = cas_logout_url + "?{}".format(generic_login_qd.urlencode()) logout_url = cas_callback_url resp = redirect(logout_url) resp.set_signed_cookie(self.TOKEN_COOKIE_NAME, token.token, httponly=True) return resp def validate_auth(self, token): # Authenticate with GenericBackend. user = authenticate(request=self.request, kfet_token=token) if user: # Log in generic user. login(self.request, user) messages.success( self.request, _("K-Fêt — Ouverture d'une session partagée.") ) resp = redirect(self.get_next_url()) else: # Try again. resp = redirect(self.request.get_full_path()) # Prevents blocking due to an invalid COOKIE. resp.delete_cookie(self.TOKEN_COOKIE_NAME) return resp def get_next_url(self): return self.request.GET.get("next", reverse("kfet.kpsul")) login_generic = GenericLoginView.as_view() @permission_required("kfet.manage_perms") def account_group(request): user_pre = Prefetch( "user_set", queryset=User.objects.select_related("profile__account_kfet") ) groups = Group.objects.filter(name__icontains="K-Fêt").prefetch_related( "permissions", user_pre ) return render(request, "kfet/account_group.html", {"groups": groups}) class AccountGroupCreate(SuccessMessageMixin, CreateView): model = Group template_name = "kfet/account_group_form.html" form_class = GroupForm success_message = "Nouveau groupe : %(name)s" success_url = reverse_lazy("kfet.account.group") class AccountGroupUpdate(SuccessMessageMixin, UpdateView): queryset = Group.objects.filter(name__icontains="K-Fêt") template_name = "kfet/account_group_form.html" form_class = GroupForm success_message = "Groupe modifié : %(name)s" success_url = reverse_lazy("kfet.account.group")