Search code examples
pythondjangocas

Using django-cas-ng to authenticate on admin site


I'm using django-cas-ng framework to authenticate users. The main problem is that the admin page still uses the default login view.

Methods used this far:

1.- Using env var

From docs:

CAS_ADMIN_PREFIX: The URL prefix of the Django administration site. If undefined, the CAS middleware will check the view being rendered to see if it lives in django.contrib.admin.views.


2.- Redirecting url on app/urls.py:

url(r'^arta/admin/login$',  django_cas_ng.views.login, name='cas_ng_login')

Everything is just ignored and the admin login form is shown.

The goal of this is to only authenticate with CAS and redirect the current /app/admin/login to CAS


Solution

  • If anyone is interested in the answer, the solution was overriding AdminSite. Django admin module overrides it's own url redirects, so editing them on /app/urls.py was useless.

    Creating an /app/admin.py and extending AdminSite like:

    from django.contrib.admin import AdminSite
    from functools import update_wrapper
    from django.urls import NoReverseMatch, reverse
    from django.views.decorators.cache import never_cache
    from django.views.decorators.csrf import csrf_protect
    from django.http import Http404, HttpResponseRedirect
    import django_cas_ng.views
    
    
    class Admin(AdminSite):
        def admin_view(self, view, cacheable=False):
            """
            Decorator to create an admin view attached to this ``AdminSite``. This
            wraps the view and provides permission checking by calling
            ``self.has_permission``.
    
            You'll want to use this from within ``AdminSite.get_urls()``:
    
                class MyAdminSite(AdminSite):
    
                    def get_urls(self):
                        from django.conf.urls import url
    
                        urls = super(MyAdminSite, self).get_urls()
                        urls += [
                            url(r'^my_view/$', self.admin_view(some_view))
                        ]
                        return urls
    
            By default, admin_views are marked non-cacheable using the
            ``never_cache`` decorator. If the view can be safely cached, set
            cacheable=True.
            """
            def inner(request, *args, **kwargs):
                if not self.has_permission(request):
                    if request.path == reverse('cas_ng_logout', current_app=self.name):
                        index_path = reverse('admin:index', current_app=self.name)
                        return HttpResponseRedirect(index_path)
                    # Inner import to prevent django.contrib.admin (app) from
                    # importing django.contrib.auth.models.User (unrelated model).
                    from django.contrib.auth.views import redirect_to_login
                    return redirect_to_login(
                        request.get_full_path(),
                        reverse('cas_ng_login', current_app=self.name)
                    )
                return view(request, *args, **kwargs)
            if not cacheable:
                inner = never_cache(inner)
            # We add csrf_protect here so this function can be used as a utility
            # function for any view, without having to repeat 'csrf_protect'.
            if not getattr(view, 'csrf_exempt', False):
                inner = csrf_protect(inner)
            return update_wrapper(inner, view)
    
        def get_urls(self):
            from django.conf.urls import url, include
            # Since this module gets imported in the application's root package,
            # it cannot import models from other applications at the module level,
            # and django.contrib.contenttypes.views imports ContentType.
            from django.contrib.contenttypes import views as contenttype_views
    
            def wrap(view, cacheable=False):
                def wrapper(*args, **kwargs):
                    return self.admin_view(view, cacheable)(*args, **kwargs)
                wrapper.admin_site = self
                return update_wrapper(wrapper, view)
    
            # Admin-site-wide views.
            urlpatterns = [
                url(r'^$', wrap(self.index), name='index'),
                url(r'^login/$', django_cas_ng.views.login, name='login'),
                url(r'^logout/$', django_cas_ng.views.logout, name='logout'),
                url(r'^password_change/$', wrap(self.password_change, cacheable=True), name='password_change'),
                url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True),
                    name='password_change_done'),
                url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
                url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut),
                    name='view_on_site'),
            ]
    
            # Add in each model's views, and create a list of valid URLS for the
            # app_index
            valid_app_labels = []
            for model, model_admin in self._registry.items():
                urlpatterns += [
                    url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)),
                ]
                if model._meta.app_label not in valid_app_labels:
                    valid_app_labels.append(model._meta.app_label)
    
            # If there were ModelAdmins registered, we should have a list of app
            # labels for which we need to allow access to the app_index view,
            if valid_app_labels:
                regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$'
                urlpatterns += [
                    url(regex, wrap(self.app_index), name='app_list'),
                ]
            return urlpatterns
    
    site = Admin()
    

    Then you override the methods that you want, in this case were admin_view and get_urls

    The interesting lines are:

    admin_view

     if request.path == reverse('cas_ng_logout', current_app=self.name):
     ----
     reverse('cas_ng_login', current_app=self.name)
    

    get_urls

    url(r'^login/$', django_cas_ng.views.login, name='login'),
    url(r'^logout/$', django_cas_ng.views.logout, name='logout')
    

    That will let you redirect the login and logout steps into CAS