Использование учетных записей Joomla в проекте на Django
- вторник, 5 марта 2019 г. в 00:21:04
Допустим что сайт, которым пользуются ваши пользователи, написан на Joomla, но для создания нового продукта для вашей аудитории вы выбрали связку Python/Django.
Как следствие, возникает необходимость использовать в Django учетные записи пользователей из базы данных Joomla.
Проблема однако в том, что Joomla и Django используют разные алгоритмы хэширования паролей, поэтому просто скопировать учетные записи не получится.
Почитав документацию Django, stack overflow и потратив некоторое время, получилось нижеописанное решение, которое по максимуму использует рекомендуемые практики разработки под Django.
Чтобы понимать, что происходит в нижеприведенных примерах, вы должны обладать некоторым пониманием архитектуры Django.
Также я предполагаю, что вы знаете, как развернуть Django проект, поэтому не описываю этот процесс.
Код скопирован из рабочего проекта но его легко будет подстроить под ваш проект с минимумом изменений.
Вероятно, в следующей мажорной версии Django данный код может поломаться, однако сам принцип решения останется тем же самым.
В данном руководстве я не описываю фронтэнд системы авторизации, так как:
check_joomla_password()
, проверяющую, что введенный пароль совпадает с оригинальным паролем пользователя.для подключения базы данных 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'],
},
},
}
manage.py startapp users
). В ней будет лежать бекэнд авторизации и модель пользователя Joomlapython manage.py inspectdb --database="joomla_db"
и исследуйте существующую базу данных Joomla.добавьте вашу модель в 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)
>>>
Если все работает (вы видите пользователя), то переходим к слудующему шагу. Иначе смотрите на вывод ошибок и исправляйте настройки.
Joomla не хранит пароли пользователей, но их хэш, например
$2y$10$aoZ4/bA7pe.QvjTU0R5.IeFGYrGag/THGvgKpoTk6bTz6XNkY0F2e
Начиная с версии Joomla v3.2, пароли позльзователей зашифрованы с помощью алгоритма BLOWFISH .
Так что я загрузил 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 и создайте функцию для проверки хэша, которая будет выглядеть примерно так:
# 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, поэтому я не смогу протестировать эту функцию для вас.
Теперь вы готовы создать Django бэкенд для авторизации пользователей из Joomla проекта.
прочитайте, как модифицировать систему авторизации Django
Зарегистрируйте новый бэкенд (еще не существующий) в 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',
]
Создайте бэкенд авторизации пользователей 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 заменить модель пользователя по умолчанию на свою (вышеописанную модель JoomlaUser).
Конечное решение, переносить или не переносить пользователей, принимайте на основе того, в каких взаимоотношениях будут новый и старый проект. Например, где будет происходить регистрация новых пользователей, какой сайт/приложение будет основным, и т.д.
Теперь пожалуйста добавьте соответствующие тесты и документацию, покрывающие новый код. Логика данного решения тесно переплетена с архитектурой Django и не очень очевидна, поэтому если вы не сделаете тесты/документацию сейчас, поддержка проекта в будущем усложнится.