- PVSM.RU - https://www.pvsm.ru -

Использование учетных записей Joomla в проекте на Django

Допустим что сайт, которым пользуются ваши пользователи, написан на Joomla, но для создания нового продукта для вашей аудитории вы выбрали связку Python/Django.

Как следствие, возникает необходимость использовать в Django учетные записи пользователей из базы данных Joomla.

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

Почитав документацию Django, stack overflow и потратив некоторое время, получилось нижеописанное решение, которое по максимуму использует рекомендуемые практики разработки под Django.

Предупреждения

Чтобы понимать, что происходит в нижеприведенных примерах, вы должны обладать некоторым пониманием архитектуры Django.

Также я предполагаю, что вы знаете, как развернуть Django проект, поэтому не описываю этот процесс.

Код скопирован из рабочего проекта но его легко будет подстроить под ваш проект с минимумом изменений.

Вероятно, в следующей мажорной версии Django данный код может поломаться, однако сам принцип решения останется тем же самым.

В данном руководстве я не описываю фронтэнд системы авторизации, так как:

  • какой у вас будет front-end — зависит от нужд вашего проекта (это может вообще быть Json API endpoint, например)
  • эта информация уже описана в официальных руководствах Django и разнообразных статьях для начинающих

Алгоритм

  • подключить базу данных (БД) Joomla к проекту Django
  • создать модель "JoomlaUser", представляющую пользователя из БД Joomla
  • написать функцию check_joomla_password(), проверяющую, что введенный пароль совпадает с оригинальным паролем пользователя.
  • добавить в проект новый бекенд авторизации "Joomla Auth Backend", который, при авторизации клиента в Django, будет доставать учетную запись пользователя из БД Joomla

1. Подключение к БД Joomla:

  • Прочитайте, как Django работает с несколькими базами данных [1]
  • для подключения базы данных Joomla в наш Django проект, добавьте следующий код в файл с настройками проекта /project_name/settings.py:

    DATABASES = {
    # БД по умолчанию 
    'default': {
        ...
    },
    
    'joomla_db': {
        'ENGINE': 'django.db.backends.mysql',
        'OPTIONS': {},
        'NAME': 'joomla_database_name',
        # Don't store passwords in the code, instead use env vars:
        'USER':     os.environ['joomla_db_user'],
        'PASSWORD': os.environ['joomla_db_pass'],
        'HOST': 'joomla_db_host, can be localhost or remote IP',
        'PORT': '3306',
    }
    }

Далее, в этом же файле, добавляем роутер для БД, который будет перенаправлять запросы от модели JoomlaUser к правильной БД:

# ensure that Joomla users are populated from the right database:
DATABASE_ROUTERS = ['manager.router.DatabaseAppsRouter']
DATABASE_APPS_MAPPING = {'joomla_users': 'joomla_db'}

# you also can create your own database router:
# https://docs.djangoproject.com/en/dev/topics/db/multi-db/#automatic-database-routing

При необходимости, в этом же файле с настройками проекта, можно включить логирование запросов к БД:

# add logging to see DB requests:
LOGGING = {
    'version': 1,
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'level': 'DEBUG',
            'handlers': ['console'],
        },
    },
}

2. создайте модель JoomlaUser

  • Прочитайте, как модель Django может использовать существующую БД [2]
  • Подумайте, где расположить новую модель "JoomlaUser". В моем проекте я создал application с именем "users" (manage.py startapp users). В ней будет лежать бекэнд авторизации и модель пользователя Joomla
  • запустите python manage.py inspectdb --database="joomla_db" и исследуйте существующую базу данных Joomla.
  • в частности нас интересует какие поля содержит таблица учетных записей и их репрезентация в виде полей модели Django
  • добавьте вашу модель в users/models.py:

    class JoomlaUser(models.Model):
    """ Represents our customer from the legacy Joomla database. """
    
    username = models.CharField(max_length=150, primary_key=True)
    email = models.CharField(max_length=100)
    password = models.CharField(max_length=100)
    # you can copy more fields from `inspectdb` output, 
    # but it's enough for the example
    
    class Meta:
        # joomla db user table. WARNING, your case can differs.
        db_table = 'live_users'
        # readonly 
        managed = False
        # tip for the database router, see "settings.DATABASE_APPS_MAPPING"
        app_label = "joomla_users"  

Запускайте терминал Django и попробуйте вытащить существующего пользователя: python manage.py shell

>>> from users.models import JoomlaUser
>>> print(JoomlaUser.objects.get(username='someuser'))
JoomlaUser object (someusername)
>>> 

Если все работает (вы видите пользователя), то переходим к слудующему шагу. Иначе смотрите на вывод ошибок и исправляйте настройки.

3. Проверка пароля учетной записи Joomla

Joomla не хранит пароли пользователей, но их хэш, например
$2y$10$aoZ4/bA7pe.QvjTU0R5.IeFGYrGag/THGvgKpoTk6bTz6XNkY0F2e

Начиная с версии Joomla v3.2, пароли позльзователей зашифрованы с помощью алгоритма BLOWFISH [3] .

Так что я загрузил python код с этим алгоритмом:

pip install bcrypt
echo bcrypt >> requirements.txt

И создал функцию для проверки паролей в файле users/backend.py:

def check_joomla_password(password, hashed):
    """
    Check if password matches the hashed password,
    using same hashing method (Blowfish) as Joomla >= 3.2

    If you get wrong results with this function, check that
    the Hash starts from prefix "$2y", otherwise it is 
    probably not a blowfish hash

    :return: True/False
    """
    import bcrypt
    if password is None:
        return False
    # bcrypt requires byte strings
    password = password.encode('utf-8')
    hashed = hashed.encode('utf-8')

    return hashed == bcrypt.hashpw(password, hashed)

Внимание! Joomla версии ниже чем 3.2 использует другой метод хеширования (md5+salt), так что эта функция не будет работать. В таком случае почитайте
обсуждение на Stackoverflow [4] и создайте функцию для проверки хэша, которая будет выглядеть примерно так:

# WARNING - THIS FUNCTION WAS NOT TESTED WITH REAL JOOMLA USERS
# and definitely has some errors
def check_old_joomla_password(password, hashed):
    from hashlib import md5
    password = password.encode('utf-8')
    hashed = hashed.encode('utf-8')
    if password is None:
        return False

    # check carefully this part:
    hash, salt = hashed.split(':')
    return hash == md5(password+salt).hexdigest()

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

4. Бэкенд авторизации пользователей Joomla

Теперь вы готовы создать Django бэкенд для авторизации пользователей из Joomla проекта.

  1. прочитайте, как модифицировать систему авторизации Django [5]

  2. Зарегистрируйте новый бэкенд (еще не существующий) в project/settings.py:

    AUTHENTICATION_BACKENDS = [
    # Check if user already in the local DB
    # by using default django users backend
    'django.contrib.auth.backends.ModelBackend',
    
    # If user was not found among django users,
    # use Joomla backend, which:
    #   - search for user in Joomla DB
    #   - check joomla user password
    #   - copy joomla user into Django user.
    'users.backend.JoomlaBackend',
    ]

  3. Создайте бэкенд авторизации пользователей Joomla в users/backend.py

from django.contrib.auth.models import User
from .models import JoomlaUser

def check_joomla_password(password, hashed):
    # this is a fuction, that we wrote before
    ...

class JoomlaBackend:
    """ authorize users against Joomla user records """
    def authenticate(self, request, username=None, password=None):
        """
        IF joomla user exists AND password is correct:
            create django user
            return user object 
        ELSE:
            return None
        """
        try:
            joomla_user = JoomlaUser.objects.get(username=username)
        except JoomlaUser.DoesNotExist:
            return None
        if check_joomla_password(password, joomla_user.password):
            # Password is correct, let's create and return Django user,
            # identical to Joomla user:

            # but before let's ensure there is no same username
            # in DB. That could happen, when user changed password
            # in Joomla, but Django doesn't know that
            User.objects.filter(username=username).delete()  

            return User.objects.create_user(
                username=username,
                email=joomla_user.email,
                password=password,
                # any additional fields from the Joomla user:
                ...
            )

    # this method is required to match Django Auth Backend interface
    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

Итог

Поздравляю — теперь пользователи вашего существующего Joomla сайта могут использовать свои учётные данные на новом сайте/приложении.

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

Как вариант, вы можете не захотеть копировать сущности пользователей из старой системы в новую.

В таком случае вот вам ссылка на статью, в которой описывается, как в Django заменить модель пользователя по умолчанию на свою [6] (вышеописанную модель JoomlaUser).

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

Тестирование и документация

Теперь пожалуйста добавьте соответствующие тесты и документацию, покрывающие новый код. Логика данного решения тесно переплетена с архитектурой Django и не очень очевидна, поэтому если вы не сделаете тесты/документацию сейчас, поддержка проекта в будущем усложнится.

Автор: AcckiyGerman

Источник [7]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/web-razrabotka/310567

Ссылки в тексте:

[1] Прочитайте, как Django работает с несколькими базами данных: https://docs.djangoproject.com/en/dev/topics/db/multi-db/

[2] Прочитайте, как модель Django может использовать существующую БД: https://docs.djangoproject.com/en/2.1/howto/legacy-databases/

[3] BLOWFISH: https://en.wikipedia.org/wiki/Blowfish_(cipher)

[4] обсуждение на Stackoverflow: https://stackoverflow.com/questions/10428126/joomla-password-encryption

[5] прочитайте, как модифицировать систему авторизации Django: https://docs.djangoproject.com/en/dev/topics/auth/customizing/

[6] вот вам ссылка на статью, в которой описывается, как в Django заменить модель пользователя по умолчанию на свою: https://wsvincent.com/django-custom-user-model-tutorial/

[7] Источник: https://habr.com/ru/post/442592/?utm_campaign=442592