📜 ⬆️ ⬇️

Using Joomla Accounts in a Django Project

Suppose that the site your users use is written in Joomla, but to create a new product for your audience, you chose a bunch of Python / Django.


As a result, it becomes necessary to use user accounts from Joomla database in Django.


The problem, however, is that Joomla and Django use different password hashing algorithms, so it’s impossible to just copy accounts.


After reading the Django documentation, stack overflow, and spending some time, the solution described below is obtained, which uses the recommended development practices for Django to the maximum.


Warnings


This architectural solution may not be suitable for you, see the discussion in the comments .


To understand what is happening in the examples below, you need to have some understanding of the Django architecture.


I also assume that you know how to deploy a Django project, so I don’t describe this process.


The code is copied from the working draft, but it will be easy to customize it for your project with a minimum of changes.


Probably, in the next major version of Django, this code may break, but the very principle of the solution will remain the same.


In this guide, I do not describe the frontend authorization system, since:



Algorithm



1. Connecting to the Joomla database:



If necessary, in the same file with the project settings, you can enable logging of queries to the database:


 # 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. create a JoomlaUser model



Next, we need to make sure that the model will refer to the correct database. To do this, add to the project a router for queries to different databases , which will redirect requests from the JoomlaUser model to its native database.


  1. Create a file "db_routers.py" in the main project folder (in the same place as your "settings.py"):


     # project_name/db_routers.py class DbRouter: """this router makes sure that django uses legacy 'Joomla' database for models, that are stored there (JoomlaUser)""" def db_for_read(self, model, **kwargs): if model._meta.app_label == 'joomla_user': return 'joomla_db' return None def db_for_write(self, model, **kwargs): if model._meta.app_label == 'joomla_user': return 'joomla_db' return None 

  2. register a new router in settings.py :


     # ensure that Joomla users are populated from the right database: DATABASE_ROUTERS = ['project_name.db_routers.DbRouter'] 


Now you can get an account from the old database.
Start a Django terminal and try to pull out an existing user: python manage.py shell


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

If everything works (you see the user), then go to the next step. Otherwise, look at the error output and correct the settings.


3. Password check Joomla account


Joomla does not store user passwords, but their hash, for example
$2y$10$aoZ4/bA7pe.QvjTU0R5.IeFGYrGag/THGvgKpoTk6bTz6XNkY0F2e


Starting with Joomla v3.2, user passwords are encrypted using the BLOWFISH algorithm.


So I loaded the python code with this algorithm:


 pip install bcrypt echo bcrypt >> requirements.txt 

And created a function to check passwords in the 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) 

Attention! Joomla version lower than 3.2 uses another hashing method (md5 + salt), so this function will not work. In this case, read
discussion on Stackoverflow and create a function to test the hash, which will look something like this:


 # 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() 

Unfortunately, I don’t have the user base from the old version of Joomla at hand, so I won’t be able to test this feature for you.


4. Backend authorization of Joomla users


Now you are ready to create a Django backend to authorize users from the Joomla project.


  1. read how to modify the Django authorization system


  2. Register a new backend (not yet existing) in 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. Create a Joomla user authorization backend in 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 

Total


Congratulations - now users of your existing Joomla site can use their credentials on the new site / application.


As active users are authorized through the new interface, they will be copied one by one into the new database.


Alternatively, you may not want to copy user entities from the old system to the new one.


In this case, here's a link to an article that describes how in Django to replace the default user model with your own (the above described JoomlaUser model).


The final decision, whether or not to transfer users, is made based on the relationships in which the new and old projects will be. For example, where will the registration of new users take place, which site / application will be the main one, etc.


Testing and documentation


Now please add the relevant tests and documentation covering the new code. The logic of this solution is closely intertwined with the architecture of Django and is not very obvious, so if you don’t do the tests / documentation now, support for the project will become more complicated in the future.


')

Source: https://habr.com/ru/post/442592/


All Articles