Create users in LDAP using Django - django

I am having trouble with the LDAP authentification module django-auth-ldap. I am using the example configuration from this site: http://packages.python.org/django-auth-ldap/
I'd like to do two things:
1) Authentificate against LDAP:
For the moment, my LDAP database is empty, I didn't add anything to it, in fact I don't know how to. However, I still am able to log in into my django-based site with my old logins/passwords stored in my django database. Why is that? Shouldn't this be ignored, shouldn't the login process occur with LDAP user/passwords instead? In other words, if my LDAP database is empty, shouldn't every single of my login fail? However, it doesn't, I have the impression that django completly ignores the django-auth-ldap module.
2) Synchronize LDAP with django (and not the other way around)
I don't want to use an existing user database to authentificate against. I want to be able to create new users in Django and propagate these users to LDAP so they can be shared by other services, in my case, an openfire server. How do you do that with django-auth-ldap?
Here is the copy/paste of my configuration:
# Baseline configuration.
AUTH_LDAP_SERVER_URI = "127.0.0.1"
AUTH_LDAP_BIND_DN = "cn=admin,dc=nodomain"
AUTH_LDAP_BIND_PASSWORD = "admin"
AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=users,dc=nodomain",
ldap.SCOPE_SUBTREE, "(uid=%(user)s)")
# Set up the basic group parameters.
AUTH_LDAP_GROUP_SEARCH = LDAPSearch("ou=django,ou=groups,dc=nodomain",
ldap.SCOPE_SUBTREE, "(objectClass=groupOfNames)"
)
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType(name_attr="cn")
# Only users in this group can log in.
AUTH_LDAP_REQUIRE_GROUP = "cn=enabled,ou=django,ou=groups,dc=nodomain"
# Populate the Django user from the LDAP directory.
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn",
"email": "mail"
}
AUTH_LDAP_PROFILE_ATTR_MAP = {
"employee_number": "employeeNumber"
}
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
"is_active": "cn=active,ou=django,ou=groups,dc=nodomain",
"is_staff": "cn=staff,ou=django,ou=groups,dc=nodomain",
"is_superuser": "cn=superuser,ou=django,ou=groups,dc=nodomain"
}
AUTH_LDAP_ALWAYS_UPDATE_USER = True
AUTH_LDAP_FIND_GROUP_PERMS = True
AUTH_LDAP_CACHE_GROUPS = True
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
# Keep ModelBackend around for per-user permissions and maybe a local
# superuser.
AUTHENTICATION_BACKENDS = (
'django_auth_ldap.backend.LDAPBackend',
'django.contrib.auth.backends.ModelBackend',
)
Sorry I don't know much about LDAP, I just installed it this morning so my question may sound naive. I just need a centralized user base that I would be able to update and share between several servers.
Thanks very much for your help.

1) Your configuration has two authentication backends installed:
AUTHENTICATION_BACKENDS = ( 'django_auth_ldap.backend.LDAPBackend', 'django.contrib.auth.backends.ModelBackend', )
Django will attempt to authenticate against each one in turn until it finds one that succeeds (or until it runs out). Since your LDAP directory is empty, it will presumably always fail, so ModelBackend will always get a shot. If you don't want to authenticate users against the Django user database, you have to remove ModelBackend from the list.
2) django-auth-ldap doesn't propagate Django users up to LDAP, only the other way around. It's designed to allow Django deployments to authenticate against existing LDAP services that are managed separately. To manipulate the contents of an LDAP directory from a Django app you might want to look at django-ldapdb.

Related

django authentication with ldap3

I am testing django_python3_ldap library and using the same ldap server that I found in the tutorial (https://ldap3.readthedocs.io/en/latest/tutorial_intro.html), but there is something which I didn't get right to stablish the connection. Does anyone know what is wrong with these settings configuration?
This is the error:
"LDAP bind failed: LDAPInvalidCredentialsResult - 49 - invalidCredentials - None - None - bindResponse - None"
This is my code in settings.py:
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"django_python3_ldap.auth.LDAPBackend",
]
# The URL of the LDAP server.
LDAP_AUTH_URL = "ldap://ipa.demo1.freeipa.org"
# Initiate TLS on connection.
LDAP_AUTH_USE_TLS = False
# The LDAP search base for looking up users.
LDAP_AUTH_SEARCH_BASE = "uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org" #"dc=demo1,dc=freeipa,dc=org"
# The LDAP class that represents a user.
LDAP_AUTH_OBJECT_CLASS = "*"
# User model fields mapped to the LDAP
# attributes that represent them.
LDAP_AUTH_USER_FIELDS = {
"username": "sAMAccountName", #"username",
"first_name": "givenName",
"last_name": "sn",
"email": "mail",
}
# A tuple of django model fields used to uniquely identify a user.
LDAP_AUTH_USER_LOOKUP_FIELDS = ("username",)
# Path to a callable that takes a dict of {model_field_name: value},
# returning a dict of clean model data.
# Use this to customize how data loaded from LDAP is saved to the User model.
LDAP_AUTH_CLEAN_USER_DATA = "django_python3_ldap.utils.clean_user_data"
# Path to a callable that takes a user model, a dict of {ldap_field_name: [value]}
# a LDAP connection object (to allow further lookups), and saves any additional
# user relationships based on the LDAP data.
# Use this to customize how data loaded from LDAP is saved to User model relations.
# For customizing non-related User model fields, use LDAP_AUTH_CLEAN_USER_DATA.
LDAP_AUTH_SYNC_USER_RELATIONS = "django_python3_ldap.utils.sync_user_relations"
# Path to a callable that takes a dict of {ldap_field_name: value},
# returning a list of [ldap_search_filter]. The search filters will then be AND'd
# together when creating the final search filter.
LDAP_AUTH_FORMAT_SEARCH_FILTERS = "django_python3_ldap.utils.format_search_filters"
# Path to a callable that takes a dict of {model_field_name: value}, and returns
# a string of the username to bind to the LDAP server.
# Use this to support different types of LDAP server.
LDAP_AUTH_FORMAT_USERNAME = "django_python3_ldap.utils.format_username_openldap"
# Sets the login domain for Active Directory users.
LDAP_AUTH_ACTIVE_DIRECTORY_DOMAIN = ""
# The LDAP username and password of a user for querying the LDAP database for user
# details. If None, then the authenticated user will be used for querying, and
# the `ldap_sync_users` command will perform an anonymous query.
LDAP_AUTH_CONNECTION_USERNAME = "admin"
LDAP_AUTH_CONNECTION_PASSWORD = "Secret123"
ldap3 always authorizes using the displayName field. If the values of your LDAP_AUTH_USER_FIELDS["username"] fields are not equal to the values of the displayName fields, you will have errors.

Django authentication with basic auth with LDAP support and no User model

This is my first Django project so please bear with me. I am creating a pure DRF project.
My requirement is that the user will call the REST APIs while sending the user creds in basic auth format which I need to validate against a LDAP server.
Also, each API with have the credentials sent and so I do not need to store the credentials at my end (in the default USER model)
I was aiming to use :
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
)
} and
INSTALLED_APPS = [
'django.contrib.auth',
}
to be able to read the basic auth creds and then hoping to hook in:
AUTHENTICATION_BACKENDS = [
'django_auth_ldap.backend.LDAPBackend',
]
to authenticate the user at LDAP server.
questions:
-- is there any better approach ?
-- since I am only passing on the credentials to LDAP server to know if the creds are valid, I do not need the USER table in the database. I need it only for object modeling. How can I acheive this? would a Proxy model help? Can we have an abstract model as the AUTH_USER_MODEL?
or using a remote user authentication backend a better option?
To make a short answer, Yes you can make a class that replaces the User model, Like this:
class User(object):
def __init__(self, authenticated=False, email=None):
self.is_authenticated = authenticated
self.email = email
The real question is how to let your Authentication middleware returns this in your request.user. There are multiple ways (I've never used django-auth-ldap so may be I'm not writing the best practice for it.):
One solution could be:
is to inherit from this class django_auth_ldap.backend.LDAPBackend https://django-auth-ldap.readthedocs.io/en/latest/reference.html#django_auth_ldap.backend.LDAPBackend
from django_auth_ldap.backend import LDAPBackend
class CustomLDAP(LDAPBackend)
def get_user_model(self):
# return your custom user
and then you need to AUTHENTICATION_BACKENDS to
AUTHENTICATION_BACKENDS = [
'path.to.CustomLDAP',
]
I'm not sure if you want this DEFAULT_AUTHENTICATION_CLASSES
if you are working with django-auth-ldap. May be this LDAP authentication with django REST
also help
READ THIS FIRST
I think also here https://django-auth-ldap.readthedocs.io/en/latest/users.html they explain it well. So, may be the solution lies in package itself

Can't connect to MongoDB via flask_pymongo

I just encountered a problem while playing with Flask and MongoDB. Here are scenarios
With Authorization Enabled
If I use PyMongo() from flask_pymongo with mongo = PyMongo(app) pattern, I just get Authorization Errors. While I am able to successfully retrieve database via MonogoClient() from pymongo using same connection string.
Without Authorization Enabled
If I use flask_pymongo, no errors. But I am not getting any data either. For example, mongo.db.collection_names() just returns empty array. With MongoClient(), the same operation was successful.
I am using:
python = 3.6.3
flask = 0.12.2
flask_pymongo = 0.5.1
pymongo = 3.5.1
Thanks in Advance
I've been using Flask with Flask-PyMongo for a while now, and below is how I make my connections to a mongodb.
from flask import Flask
from flask_cors import CORS
from flask_pymongo import PyMongo
app = Flask(__name__)
CORS(app) # very important!
USERNAME = '<username>'
PASSWORD = '<password>'
PORT = '27017'
HOST = 'localhost'
app.config['MONGO_DBNAME'] = '<DB Name>'
app.config['MONGO_HOST'] = HOST
app.config['MONGO_PORT'] = PORT
app.config['MONGO_USERNAME'] = USERNAME
app.config['MONGO_PASSWORD'] = PASSWORD
mongo = PyMongo(app)
You may not need the CORS(app) depending on your setup.
To create multiple connections do the following:
# Users db
app.config['MONGO2_DBNAME'] = 'users'
app.config['MONGO2_HOST'] = HOST
app.config['MONGO2_PORT'] = PORT
app.config['MONGO2_USERNAME'] = USERNAME
app.config['MONGO2_PASSWORD'] = PASSWORD
users_mongo = PyMongo(app, config_prefix='MONGO2')
I should also note that I had problems connecting with authorization through Flask-PyMongo because the database needed to have authorization configured a certain way. Flask-PyMongo expects the db authority to be on the database itself and not elsewhere. So when creating a user and password for a db do the following inside a mongo shell:
Create user admin
use admin
db.createUser({user: "userAdmin", pwd: "", roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] } )
Create user on YOUR db
use <YOUR db>
db.createUser({user: "", pwd: "", roles:[{ role: "dbAdmin", db: ""}, {role: "readWrite", db: ""}]})
Within mongo and even with PyMongo you can create users within one database and give them access to other dbs, but Flask-PyMongo for whatever reason does not play nice here and you'll need the user created within the database you want to use.
Hope this helps as I've struggled here as well. Feel free to ask for clarification

Django user authentication: django_auth_ldap.backend.LDAPBackend

I don't understand how to use the LDAPBackend in django, all I want to do is to authenticate a user against LDAP. I have tried the following:
from django_auth_ldap.backend import LDAPBackend
auth = LDAPBackend()
user = auth.authenticate(username='my_uid',password='pwd')
At this point user is None and looking at tcpdump I can't see any connection attempt to the LDAP server.
settings.py
AUTH_LDAP_SERVER_URI = 'ldap.example.com'
AUTH_LDAP_USER_DN_TEMPLATE = 'uid=%(user)s,ou=People,dc=example,dc=com'
AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = True
AUTH_LDAP_CACHE_GROUPS = True
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn",
"email": "mail"
}
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'django_auth_ldap.backend.LDAPBackend',
)
The official django doc doesn't provide any snippet about how to use this backend in a view.
Many thanks for your help!
All of the documentation for django-auth-ldap is here. For debugging your configuration, you'll want to install a logging handler on the 'django_auth_ldap' logger; see Django's logging documentation for more on that.
At a glance, I would say that one problem is that AUTH_LDAP_SERVER_URI is not set to a URI; try something of the form ldap://ldap.example.com/. You'll also want to review the documentation for AUTH_LDAP_BIND_AS_AUTHENTICATING_USER: this is an advanced and somewhat subtle option that you should only enable if you know that you need it.

Migrate user accounts from Joomla to Django

I'm overhauling a site I'd originally made using Joomla to Django, and I was wondering if I can import the user records directly from Joomla (my main concern is the user passwords as they are encrypted).
Yes, you can, but you'll have to do some work. Joomla keeps users in some specific DB table structure, so you'll have to pull them out and insert them into a users table you create in your Django application. As for encryption, if the algorithm is known, it's probably the hash value that's kept in the DB, and you can just transfer it as-is as long as you implement the same hashing algorithm in your Django application.
Remember: Django is a more general 'concept' than Joomla - it's a framework for writing web application, hence in theory you can even re-implement Joomla completely with it.
Joomla users in Django (Django auth backend, that populates users from Joomla)
Once I was in need to use our existing Joomla users in my new API, written in Django.
Problem is that I could not just copy Joomla users into a Django database, because:
Joomla password hashing system differs from Django one.
J-users and D-users had different set of fields (this is easy to fix, but still)
So instead I made a custom auth backend for Django, and now I can confidently say that
Django can authenticate users against the Joomla database, without need to decrypt password hashes or to copy all users from Joomla DB at once.
Algorithm:
connect the Joomla database to the Django project
create JoomlaUser model, to populate users from the Joomla DB
implement check_joomla_password() function, that validates user passwords the same way as Joomla
add custom "Joomla Auth Backend" that copies each user from Joomla to Django at the first login
Implementation:
To understand what's going on, you should have some experience with Django.
The code have to be modified accordingly to your django project.
However the code is taken from the working project with minimum changes,
and it should be easy to set up for your needs.
1. connect to Joomla DB:
Read https://docs.djangoproject.com/en/dev/topics/db/multi-db/
Add to /project_name/settings.py:
DATABASES = {
'default': {"your default DB settings"},
'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',
}
}
# 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 Joomla user model
Read https://docs.djangoproject.com/en/2.1/howto/legacy-databases/
Think where to keep new "Joomla user" model.
In my project I've created 'users' app, where my custom user models live,
and the custom Joomla backend will be placed.
inspect how the user is stored in the existing Joomla DB:
python manage.py inspectdb --database="joomla_db"
Find and carefully examine the users table.
Add to 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
app_label = "joomla_users"
To ensure, that JoomlaUser model will use right DB, add a database router:
Create file "db_routers.py" in the project folder, where the "settings.py" file is stored:
# 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_users':
return 'joomla_db'
return None
def db_for_write(self, model, **kwargs):
if model._meta.app_label == 'joomla_users':
return 'joomla_db'
return None
register new router, for that, add in settings.py:
# ensure that Joomla users are populated from the right database:
DATABASE_ROUTERS = ['project_name.db_routers.DbRouter']
Now go to django shell ./manage.py shell and try to populate some users, e.g.
>>> from users.models import JoomlaUser
>>> print(JoomlaUser.objects.get(username='someuser'))
JoomlaUser object (someuser)
>>>
If everything works - move on to the next step. Otherwise look into errors, fix settings, etc
3. Check Joomla user passwords
Joomla does not store user password, but the password hash, e.g.
$2y$10$aoZ4/bA7pe.QvjTU0R5.IeFGYrGag/THGvgKpoTk6bTz6XNkY0F2e
Starting from Joomla v3.2, user passwords are hashed using BLOWFISH algorithm.
So I've downloaded a python blowfish implementation:
pip install bcrypt
echo bcrypt >> requirements.txt
And created Joomla password check function 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 from Joomla.
: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)
Old versions Warning! Joomla < 3.2 uses different hashing method (md5+salt),
so this function won't work.
In this case read joomla password encryption
and implement a hash checker in python, which probably will look something like:
# WARNING - THIS FUNCTION 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 have no old Joomla instance running, thus I couldn't test this function for you.
4. Joomla Authentication Backend
Now you are ready to create a Joomla authentication backend for Django.
read how to modify django auth backends: https://docs.djangoproject.com/en/dev/topics/auth/customizing/
Register Jango (not yet existing) backend in the 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',
]
Create Joomla authentication Backend in users/backend.py:
from django.contrib.auth.models import User
from .models import JoomlaUser
""" check password function we wrote before """
def check_joomla_password(password, hashed):
...
class JoomlaBackend:
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 identical Django user:
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
Test & documentation
Congratulations - now your customers from old Joomla site can use their credentials on the new Django site or rest api, etc
Now, add proper tests and documentation to cover this new code.
It's logic is quite tricky, so if you won't make tests&docs (lazy dude) - maintaining the project will be a pain in your (or somebody's else) ass.
Kind regards,
# Dmytro Gierman
Update 11.04.2019 - errors fixed.
I think there is 3 ways to approach this problem:
1) You can read about how joomla and django make hash of passwords and make the migration with a script
2) You can make your own authentication backend
3) You can use a ETL tool
Joomla (PHP) is a CMS while Django (Python) is a web framework.
I wonder whether this is really possible. What i can conclude at this point of time is that it is not possible. However someone may have any idea about this.
Thanks :)