Лучший способ сделать login_required Django значением по умолчанию

Я работаю над большим приложением Django, подавляющее большинство которого требует входа в систему доступа. Это означает, что все всюду по нашему приложению мы опрыснули:

@login_required
def view(...):

Это прекрасно, и это работает отлично, пока мы не забываем добавлять его везде! Печально иногда мы забываем, и отказ часто не ужасно очевиден. Если единственная ссылка на представление будет на @login_required странице тогда, то Вы вряд ли заметите, что можно на самом деле достигнуть того представления без входа в систему. Но плохие парни могли бы заметить, который является проблемой.

Моя идея состояла в том, чтобы инвертировать систему. Вместо того, чтобы иметь необходимость ввести @login_required везде, вместо этого у меня было бы что-то как:

@public
def public_view(...):

Только для общедоступного материала. Я пытался реализовать это с некоторым промежуточным программным обеспечением, и я, могло казаться, не заставил его работать. Все, что я попробовал, взаимодействовало плохо с другим промежуточным программным обеспечением, которое мы используем, я думаю. Затем я пытался писать что-то для пересечения шаблонов URL, чтобы проверить, что все, это не @public, было отмечено @login_required - по крайней мере тогда мы получили бы быструю ошибку, если бы мы забыли что-то. Но тогда я не мог выяснить, как сказать, был ли @login_required применен к представлению...

Так, что правильный путь состоит в том, чтобы сделать это? Спасибо за справку!

96
задан samtregar 29 January 2010 в 18:08
поделиться

4 ответа

Промежуточное ПО может быть вашим лучшим выбором. Я использовал этот фрагмент кода в прошлом, измененный из фрагмента, найденного в другом месте:

import re

from django.conf import settings
from django.contrib.auth.decorators import login_required


class RequireLoginMiddleware(object):
    """
    Middleware component that wraps the login_required decorator around
    matching URL patterns. To use, add the class to MIDDLEWARE_CLASSES and
    define LOGIN_REQUIRED_URLS and LOGIN_REQUIRED_URLS_EXCEPTIONS in your
    settings.py. For example:
    ------
    LOGIN_REQUIRED_URLS = (
        r'/topsecret/(.*)$',
    )
    LOGIN_REQUIRED_URLS_EXCEPTIONS = (
        r'/topsecret/login(.*)$',
        r'/topsecret/logout(.*)$',
    )
    ------
    LOGIN_REQUIRED_URLS is where you define URL patterns; each pattern must
    be a valid regex.

    LOGIN_REQUIRED_URLS_EXCEPTIONS is, conversely, where you explicitly
    define any exceptions (like login and logout URLs).
    """
    def __init__(self):
        self.required = tuple(re.compile(url) for url in settings.LOGIN_REQUIRED_URLS)
        self.exceptions = tuple(re.compile(url) for url in settings.LOGIN_REQUIRED_URLS_EXCEPTIONS)

    def process_view(self, request, view_func, view_args, view_kwargs):
        # No need to process URLs if user already logged in
        if request.user.is_authenticated():
            return None

        # An exception match should immediately return None
        for url in self.exceptions:
            if url.match(request.path):
                return None

        # Requests matching a restricted URL pattern are returned
        # wrapped with the login_required decorator
        for url in self.required:
            if url.match(request.path):
                return login_required(view_func)(request, *view_args, **view_kwargs)

        # Explicitly return None for all non-matching requests
        return None

Затем в settings.py перечислите базовые URL-адреса, которые вы хотите защитить:

LOGIN_REQUIRED_URLS = (
    r'/private_stuff/(.*)$',
    r'/login_required/(.*)$',
)

Пока ваш сайт следует соглашениям об URL-адресах для страницы, требующие аутентификации, эта модель будет работать. Если это не подходит один-к-одному, вы можете изменить промежуточное программное обеспечение в соответствии с вашими обстоятельствами.

Что мне нравится в этом подходе - помимо устранения необходимости засорять кодовую базу декораторами @login_required , - это то, что при изменении схемы аутентификации у вас есть одно место, куда можно перейти, чтобы внести глобальные изменения.

93
ответ дан 24 November 2019 в 05:41
поделиться

Существует альтернатива для создания декоратора на каждую функцию представления. Вы также можете поставить Login_Required () декоратор в файле URLS.PY . Хотя это все еще ручная задача, по крайней мере, у вас все это в одном месте, что облегчает аудит.

E.g.,

    from my_views import home_view

    urlpatterns = patterns('',
        # "Home":
        (r'^$', login_required(home_view), dict(template_name='my_site/home.html', items_per_page=20)),
    )

Обратите внимание, что функции просмотра называются и импортируются непосредственно, а не как строки.

также обратите внимание, что это работает с любым приоминимым объектом просмотра, включая классы.

29
ответ дан 24 November 2019 в 05:41
поделиться

Трудно изменить встроенные предположения в Django, не перенесеняя, как передается URL-адреса для просмотра функций.

Вместо того, чтобы потушить в интерналах Django, вот аудит, который вы можете использовать. Просто проверьте каждую функцию просмотра.

import os
import re

def view_modules( root ):
    for path, dirs, files in os.walk( root ):
        for d in dirs[:]:
            if d.startswith("."):
                dirs.remove(d)
        for f in files:
            name, ext = os.path.splitext(f)
            if ext == ".py":
                if name == "views":
                    yield os.path.join( path, f )

def def_lines( root ):
    def_pat= re.compile( "\n(\S.*)\n+(^def\s+.*:$)", re.MULTILINE )
    for v in view_modules( root ):
        with open(v,"r") as source:
            text= source.read()
            for p in def_pat.findall( text ):
                yield p

def report( root ):
    for decorator, definition in def_lines( root ):
        print decorator, definition

Запустите это и изучите вывод для def S без соответствующих декоративов.

2
ответ дан 24 November 2019 в 05:41
поделиться

Вы не можете действительно выиграть это. Вы просто должны сделать заявление о требованиях авторизации. Где еще вы бы положили эту декларацию, за исключением праву по функции просмотра?

Рассмотрим замену функций вида на Callable объекты.

class LoginViewFunction( object ):
    def __call__( self, request, *args, **kw ):
        p1 = self.login( request, *args, **kw )
        if p1 is not None:
            return p1
        return self.view( request, *args, **kw )
    def login( self, request )
        if not request.user.is_authenticated():
            return HttpResponseRedirect('/login/?next=%s' % request.path)
    def view( self, request, *args, **kw ):
        raise NotImplementedError

Затем вы делаете свои функции представления подклассов loginViewfunctionfunction .

class MyRealView( LoginViewFunction ):
    def view( self, request, *args, **kw ):
        .... the real work ...

my_real_view = MyRealView()  

Это не сохраняет никаких строк кода. И это не помогает «мы забыли» проблемы. Все, что вы можете сделать, это изучить код, чтобы убедиться, что функции просмотра являются объектами. Правого класса.

Но даже тогда вы никогда не узнаете, что Каждый функция просмотра верна без единичного тестового набора.

0
ответ дан 24 November 2019 в 05:41
поделиться
Другие вопросы по тегам:

Похожие вопросы: