Django LDAP Integration with Jumpcloud Open LDAP (2023)

Problem Statement:- The task is to use Jump cloud open LDAP for our Django app for authentication and as well authorization in our app

Background:- We all have faced managing the user data at different place but this can come under one hood that is with the help of Jumpcloud. I am not going into detail what all features it provides you can check it on their page.

Prerequisite:- The requirements are below :-

  • Jumpcloud Platform Knowledge for developing your Org level solution.
  • LDAP (Lightweight Directory Access Protocol) information on this
  • Django Basic Knowledge
  • Read this blog provided by Jumpcloud for more clear understanding of things click on this link

Required packages:- The required packages are as below:-

  • django-auth-ldap ( This package helps to connect any LDAP with our Django app this basically connects with Django backend and mange's all the things)
  • python-ldap ( django-auth-ldap has a dependency on this package so it will automatically install with it) In case this package gives you an error for installation you can try to install the wheel package from this link

Basic Terminologies:- The terminologies are as follows:-

  • Bind Operation: A bind operation may be used to authenticate a client as a particular user as a means of verifying credentials, and to specify the authorization identity for subsequent operations performed on that connection.
  • CN:-Common name. ( Basically User object or Group Name)
  • OU:- Organizational Unit
  • DC :-Domain Component ( Basically Google.com or any other)
  • O:- Organization

Since Jumpcloud Open LDAP follows Open LDAP Standards you can check out this link

Process Flow:-

  • The first user tries to login
  • LDAP connection is established first with the required settings
  • User Data is fetched along with its additional attributes supported by respective LDAP and is passed to the backend
  • If User has credentials in Jump cloud LDAP or in Django Users database which is not present in the Jump cloud LDAP he will be authenticated(We can also set authenticate users only with LDAP users only )
  • The population Of users is done when a user tries to authenticate for the first time for the rest of the time it automatically updates the additional attributes if anything has been updated in the Jumpcloud users
  • Basically, For every new User, it will populate fields and for old users, it will update attributes if there have been any changes
  • Once it has been authenticated you can access the pages

Below are the steps we are going to follow for setting up our project

  1. Create Jumpcloud Account

We can create Jump cloud account free for 10 days use basics things which is enough to understand all the features provided by Jump cloud. Explore things and finally use it for our own solution. They also provide Live chat for 10 days where you can ask your questions and get answers from them apart from that they have good blogs for using their application and also Videos are available on YouTube as well.

Django LDAP Integration with Jumpcloud Open LDAP (1)

2) Get Organization and DN From your Jumpcloud OL (Open LDAP)

Django LDAP Integration with Jumpcloud Open LDAP (2)

3) We will on at least three users In our Jumpcloud for our Demonstration by adding manually Users into Jumpcloud

Django LDAP Integration with Jumpcloud Open LDAP (3)

As you can see above you can manually add users or import from csv by downloading the template and filling the users into csv and uploading the same for bulk users loading and apart from that you can do Google integration and Azure AD (Active Directory) basically importing users from the Google , azure respectively into the jumpcloud.

Django LDAP Integration with Jumpcloud Open LDAP (4)

Fill all the required data username must be unique and company mail as well fill as much details as you can below are employment details and persona details as well because we are also going to add User profile data as well like fetching additional attributes from Jump cloud and add it our User Profile Model. Remember to activate users as well by doing email verification and setting password. ( users will become active state if done properly)

Creation Of User Group

Now Django Provides three User permission that is superuser, staff ,active. We will be Using User Groups in the Jumpcloud to flag our users according to the permission for example Let say I have created user group Django_active in jumpcloud and mapped it to active permission in Django and added one of the user into it . So during our Authentication it will take whichever users are present in the Django_active group will be flagged as active in our Users database .

Django LDAP Integration with Jumpcloud Open LDAP (5)

This is the step where we create User Group so here will be required to create four User group that is First user group will be the User Group which will be basically our App Group so here we will name it as Django App. In this User group add all the users which you can do via going into Users side tab and tick all the users.

Django LDAP Integration with Jumpcloud Open LDAP (6)

Assigning User Group To JumpCloud LDAP as well

Django LDAP Integration with Jumpcloud Open LDAP (7)

Now After doing this save it and rest the remaining Groups will be active ,staff ,superuser. Follow the same Steps for these groups as well but Don't assign all users to staff and superuser it will be helpful to check if our backend is mapping properly all those permissions. ( Remember you can assign any names as you Like but we have to map those properly in our Django Settings as well)

(Video) Searching an LDAP Server

4) Checking all the Users and user Group are mapped with our Jumpcloud Ldap

Django LDAP Integration with Jumpcloud Open LDAP (8)

You can double check if Our user Groups have been mapped to our Jumpcloud LDAP by clicking On jumpcloud LDAP and going to User Groups tab and mapped group will be marked . Same goes for Users as well check the users as well if they have been marked.

5) The Final Thing remaining is to Bind a User with LDAP

Django LDAP Integration with Jumpcloud Open LDAP (9)

We have to bind a user in order to be able to set up connection with our Jump Cloud LDAP. For that just click on the None and select LDAP Bind DN. Remember you must have at least one user to be in Bind DN. You will be require this Bind DN user credentials later in the Django setup

Finally This bring us end to our setting up our Jump cloud LDAP setting Next Section is all on Django.

This is here we will create basic login the templates have been taken from other sources as well. So here we will just implement the default login in Django and also add User Profile as well to the model and also profile page where you can fetch all the profiles

1) Basic Code for the Login code snippets below:-

Django LDAP Integration with Jumpcloud Open LDAP (10)

This is below the file and folder structure for the reference purpose.

connection is the app name and ldappro is the django project name

models.py

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

# Create your models here.
class UserProfile(models.Model):
# This field is required.
user = models.OneToOneField(User,related_name='profile', on_delete=models.CASCADE)
# Other fields here
username = models.CharField(max_length=254 )
first_name = models.CharField(max_length=254 )
last_name = models.CharField(max_length=254 )
email = models.EmailField(max_length=254 )
middle_name = models.CharField(max_length=100 )
display_name = models.CharField(max_length=100 )
title = models.CharField(max_length=100 )
department = models.CharField(max_length=100 )
cost_center = models.CharField(max_length=100 )
location = models.CharField(max_length=100 )
employee_id = models.CharField(max_length=100 )
employee_type = models.CharField(max_length=100 )
company = models.CharField(max_length=100 )
description = models.CharField(max_length=100 )
phone_no = models.CharField(max_length=100 )

def __str__(self):
return self.user

Now here note that in the user profile Model I have used those fields which are supported by Jumpcloud LDAP. You can refer this link . You have to scroll down to LDAP Attribute Mapping table where you can see the mapping this is useful in further down below when we will map the fields. There are plenty of fields which we can fetch so It should be fair enough for any profile data.

admin.py

from django.contrib import admin
from .models import UserProfile

admin.site.register(UserProfile)
# Register your models here.

Here we have register our Models so that we can checkout in our Admin page

Views.py

from django.shortcuts import render ,redirect
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect
from .models import UserProfile
from django.contrib.auth.views import LoginView
from .forms import LoginForm

class CustomLoginView(LoginView):
form_class = LoginForm

def form_valid(self, form):
return super(CustomLoginView, self).form_valid(form)

@login_required
def profile(request):

try:
ldapuserprofile = request.user.profile
except UserProfile.DoesNotExist:
return HttpResponseRedirect('/login/')

context = {'ldapuser': ldapuserprofile}
return render(request, 'registration/profile.html', context)

def home(request):
return render(request, 'registration/home.html')

The above code is all about home page ,profile page and Login page

Templates Files

(Video) LDAP Server Guide - How Does It Work?

base.html

<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

<!--Font awesome icons -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">

<title>{% block title %} {% endblock %} </title>
</head>
<body>
<div class="container p-3 my-3">
<div class="row">
<div class="col-md-12">
<nav class="navbar navbar-expand-md navbar-light " style="background-color: #f0f5f5">
<a href="/" class="navbar-brand">Home</a>
<button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#navbarCollapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<div class="navbar-nav ml-auto">
{% if user.is_authenticated %}
<a href="{% url 'users-profile' %}" class="nav-item nav-link">Profile</a>
<a href="{% url 'logout' %}" class="nav-item nav-link">Logout</a>
{% else %}
<a href="{% url 'login' %}" class="nav-item nav-link">Sign in</a>
{% endif %}

</div>
</div>
</nav>
<!--Any flash messages pop up in any page because this is the base template-->
{% if messages %}
<div class="alert alert-dismissible" role="alert">
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endif %}
{% block content %}{% endblock %}
</div>
</div>
</div>

<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>

<!-- A plugin for password show/hide -->
<script src="https://unpkg.com/bootstrap-show-password@1.2.1/dist/bootstrap-show-password.min.js"></script>

</body>
</html>

home.html

{% extends "registration/base.html" %}
{% block title %} Home Page {% endblock title%}
{% block content %}
<div class="jumbotron">
<p class="lead">
This is <b> login system</b>
</p>
<hr class="my-4">
<p class="lead">
{% if user.is_authenticated %}
<p>You are welcome on Home Page</p>
<p>Your name is - {{user.first_name}} {{user.last_name}}</p>
<p>Your email is - {{user.email}}</p>
<p>Your Display name is - {{user.display_name}}</p>
<p>Your Description is - {{user.description}}</p>

<a class="btn btn-primary btn-lg" href="{% url 'logout' %}" role="button">Logout</a>
{% else %}
<a class="btn btn-primary btn-lg" href="{% url 'login' %}" role="button">Sign in</a>
{% endif %}
</p>
</div>

{% endblock content %}

profile.html

{% extends "registration/base.html" %}
{% block title %}Profile Page{% endblock title %}
{% block content %}
<div class="row my-3 p-3">
<p> </p>
</div>
{% if user_form.errors %}
<div class="alert alert-danger alert-dismissible" role="alert">
<div id="form_errors">
{% for key, value in user_form.errors.items %}
<strong>{{ value }}</strong>
{% endfor %}
</div>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endif %}

<div class="form-row">
<p>You are welcome on Home Page</p>
<p>Your name is - {{ldapuser.first_name}} {{ldapuser.last_name}}</p>
<p>Your email is - {{ldapuser.email}}</p>
<p>Your middlename is - {{ldapuser.middle_name}}</p>

</div>

{% endblock content %}

login.html

{% extends "registration/base.html" %}
{% block title %} Login Page {% endblock title%}
{% block content %}
<div class="form-content my-3 p-3">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-5">
<div class="card shadow-lg border-0 rounded-lg mt-0 mb-3">
<div class="card-header justify-content-center">
<h3 class="font-weight-light my-1 text-center">Sign In</h3>
</div>
{% if form.errors %}
<div class="alert alert-danger alert-dismissible" role="alert">
<div id="form_errors">
{% for key, value in form.errors.items %}
<strong>{{ value }}</strong>
{% endfor %}
</div>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endif %}
<div class="card-body">
<form method="POST">
{% csrf_token %}
<div class="form-row">
<div class="col-md-10 offset-md-1">
<div class="form-group">

<label class="small mb-1">Username</label>
{{ form.username }}
</div>
</div>
</div>
<div class="form-row">
<div class="col-md-10 offset-md-1">
<div class="form-group">
<label class="small mb-1">Password</label>
{{ form.password }}
</div>
</div>
</div>
<div class="form-row">
<div class="col-md-10 offset-md-1">
<div class="form-group">
<!-- Add a Remember me functionality -->
<label> Remember me</label>
</div>
</div>
</div>
<div class="form-row">
<div class="col-md-10 offset-md-1">
<div class="form-group mt-0 mb-1">
<button name="login" class="col-md-12 btn btn-dark" id="login">Sign in</button>
</div>
</div>
</div>
</form>
</div>
<div class="card-footer text-center">

</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}

logout.html

{% extends "registration/base.html" %}
{% block title %}Logout{% endblock title %}
{% block content %}
<div class="card" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">You have been logged out</h5>
<p class="card-text">
Thanks for your time, contact me for any comments or suggestions using my email address.
</p>
<br><hr><a href="{% url 'login' %}" class="btn btn-primary">Sign in again</a>
</div>
</div>

{% endblock content %}

urls.py

"""ldappro URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""

from django.contrib import admin
from django.urls import path, include
from django.contrib.auth import views as auth_views
from connection.views import *
from connection.forms import LoginForm

urlpatterns = [
path('', home, name='users-home'),
path('admin/', admin.site.urls),
path('profile/', profile, name='users-profile'),
path('login/', CustomLoginView.as_view(redirect_authenticated_user=True, template_name='registration/login.html',
authentication_form=LoginForm), name='login'),

path('logout/', auth_views.LogoutView.as_view(template_name='registration/logout.html'), name='logout'),

]

Note this urls.py is of Project urls . You can your urls as per your requirements.

import os
import logging

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = ''

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

# Application definition

(Video) what is Remote LDAP Authentication

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'connection',
]

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'ldappro.urls'

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR + '/templates/', ],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

WSGI_APPLICATION = 'ldappro.wsgi.application'

# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}

# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]

LOGIN_REDIRECT_URL = '/'
LOGIN_URL = 'login'

# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/

STATIC_URL = '/static/'

Basically here you just have to set your templates and add login and logout url. This brings us to setting up the default login and logout for your Django app. Now the last thing is of settings.py

  1. Setting up the settings.py file with LDAP settings
import ldap
import logging
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType, NestedActiveDirectoryGroupType, PosixGroupType,GroupOfUniqueNamesType

Now you can read this above what each of this imports does by going to this link and this link as well.

Now we are going to use only LDAPSearch, GroupOfNamesType This two for our demonstration rest you might be requiring for your own projects which

LdapSearch:- object that identifies the set of relevant group objects. That is, all groups that users might belong to as well as any others that we might need to know about (in the case of nested groups, for example)

Now for our requirement we are using GroupOfNamesType for our search. It is used to represent group relations. Since they allow DNs as members you can also use them to represent nested groups.

LDAP_USER_NAME= 'binded_username'
AUTH_LDAP_SERVER_URI = 'ldap://ldap.jumpcloud.com:389'
AUTH_LDAP_BIND_DN = 'uid=binded_username,ou=users,o=,dc=,dc=com'
AUTH_LDAP_BIND_PASSWORD = 'binded_user_password'
AUTH_LDAP_USER_SEARCH = LDAPSearch('ou=users,o=,dc=,dc=com', ldap.SCOPE_SUBTREE,
'(uid=%(user)s)')
AUTH_LDAP_GROUP_SEARCH = LDAPSearch('ou=users,o=,dc=,dc=com', ldap.SCOPE_SUBTREE,
'(objectClass=top)')
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType(name_attr="cn")
AUTH_LDAP_MIRROR_GROUPS = True

Later add this code as well in the settings.py file at the end

Now here binded username you have to replace with the username that earlier we had binded with Jumpcloud LDAP and password for the field of binded_user_password. You have to also add o= (organization) this value we have got during the Jumpcloud Open LDAP Second step you have got o value and dn value as well fill those missing parameters

AUTH_LDAP_BIND_DN = 'uid=tester1,ou=users,o=xyzzf,dc=abc,dc=com'

Now in the above example tester1 is username , xyzzf is organisation value and abc is domain component

By default Jumpcloud follows some format ou=users will be always same and it is must because basically what we are doing here is we are establishing connection with the open Jumpcloud LDAP using binded user and we are basically going inside org(o) inside org we are looking at organizational unit(ou) which is users here

Now inside this we will basically get our User Groups which we are basically mapping by

AUTH_LDAP_GROUP_TYPE = GroupOfNamesType(name_attr="cn")

Here we are telling to map cn as group of names so basically we are getting the group names which is cn as we have mentioned in the above line

AUTH_LDAP_REQUIRE_GROUP = "cn=Django App,ou=users,o=,dc=,dc=com"

AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn",
"email": "mail",
"username": "uid",
"password": "userPassword",
"middle_name": "initials",
"display_name": "displayName",
"title": "title",
"department": "departmentNumber",
"cost_center": "businessCategory",
"location": "physicalDeliveryOfficeName",
"employee_id": "employeeNumber",
"employee_type": "employeeType",
"company": "company",
"description": "description",
"phone_no": "mobile",

}
AUTH_PROFILE_MODULE = 'connection.UserProfile'

(Video) How to synchronize your WordPress users passwords with your LDAP server?

AUTH_LDAP_USER_FLAGS_BY_GROUP = {
"is_active": "cn=active,ou=users,o=,dc=,dc=com",
"is_staff": "cn=staff,ou=users,o=,dc=,dc=com",
"is_superuser": "cn=superuser,ou=users,o=,dc=jumpcloud,dc=com"
}

AUTH_LDAP_ALWAYS_UPDATE_USER = True
AUTH_LDAP_FIND_GROUP_PERMS = True
AUTH_LDAP_CACHE_TIMEOUT = 3600

AUTH_LDAP_FIND_GROUP_PERMS = True

# Keep ModelBackend around for per-user permissions and maybe a local
# superuser.
AUTHENTICATION_BACKENDS = (
'django_auth_ldap.backend.LDAPBackend',
'django.contrib.auth.backends.ModelBackend',
)

# logging
logger = logging.getLogger('django_auth_ldap')
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.DEBUG)
DEFAULT_AUTO_FIELD='django.db.models.AutoField'

The above code is the last piece in the setttings.py file which you have to add

Please fill the o and dc value here as well. Now here we have use cn= Django App means here we will hunt for all the members which is present inside the group Django App This is basically what AUTH_LDAP_REQUIRE_GROUP does. here it will try to match the username which is present inside this group and also authenticate with password as well if it is present.

AUTH_LDAP_USER_ATTR_MAP is nothing but we are fetching additional profile fields from jumpcloud here I have just created mapping of models field name and its corresponding JumpCloud Open LDAP attribute name. In the above section I have also added the link you can checkout there for more fields if you want to get for demonstration purpose it is more than enough

AUTH_PROFILE_MODULE = 'connection.UserProfile'

This is nothing but setting our profile model to be used as profile

AUTH_LDAP_USER_FLAGS_BY_GROUP = {
"is_active": "cn=active,ou=users,o=,dc=,dc=com",
"is_staff": "cn=staff,ou=users,o=,dc=,dc=com",
"is_superuser": "cn=superuser,ou=users,o=,dc=jumpcloud,dc=com"
}

Here as I have told earlier we will require to map the Django active, staff and superuser to the Jumpcloud group in order to be marked as true. cn is referring to the Jumpcloud group so whichever users is inside the active group will be marked in Django as active same goes for rest other as well.

# Logging section is basically you can see the logs error on the terminal regarding the LDAP and what all things it is basically using for search etc.

With this we are finally Done with the settings.py Now the last thing is using Signal to Store our profile Data by default User Profile get store in Django below 1.7 but for above version we have to code it with the help of signal.

Now this signal code is workaround type of Code.

Create a signals.py inside your app

from __future__ import unicode_literals
import django_auth_ldap.backend
from .models import UserProfile
from django.contrib.auth.models import User

from django.db.models.signals import post_save
from django.dispatch import receiver
from django.conf import settings
## LDAP Bind User NAme
LDAP_USER_NAME = getattr(settings, "LDAP_USER_NAME", None)

def populate_user_profile(sender, user=None, ldap_user=None, **kwargs):
if User.objects.filter(username=user).exists():
temp_profile = None
data = {}
try:
temp_profile = user.profile
except:
temp_profile = UserProfile.objects.create(user=user)
data['display_name'] = ldap_user.attrs.get('displayName')
data['middle_name'] = ldap_user.attrs.get('initials')
data['email']= ldap_user.attrs.get('mail')
data['first_name']= ldap_user.attrs.get('givenName')
data['last_name']= ldap_user.attrs.get('sn')
data['username']=ldap_user.attrs.get('uid')
data['title']= ldap_user.attrs.get('title')
data['department']= ldap_user.attrs.get('departmentNumber')
data['cost_center']= ldap_user.attrs.get('businessCategory')
data['location']= ldap_user.attrs.get('physicalDeliveryOfficeName')
data['employee_id']= ldap_user.attrs.get('employeeNumber')
data['employee_type']= ldap_user.attrs.get('employeeType')
data['company']= ldap_user.attrs.get('company')
data['description']= ldap_user.attrs.get('description')
data['phone_no']= ldap_user.attrs.get('mobile')

for key, value in data.items():
if value:
setattr(user.profile, key, value[0].encode('utf-8').decode())
user.profile.save()

else:
pass # This means User is logging in first time so create_user_profile signal will handle profile data

django_auth_ldap.backend.populate_user.connect(populate_user_profile)

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
profile = UserProfile.objects.create(user=instance)
data={}
data['display_name'] = instance.ldap_user.attrs.get('displayName')
data['middle_name'] = instance.ldap_user.attrs.get('initials')
data['email'] = instance.ldap_user.attrs.get('mail')
data['first_name'] = instance.ldap_user.attrs.get('givenName')
data['last_name'] = instance.ldap_user.attrs.get('sn')
data['username'] = instance.ldap_user.attrs.get('uid')
data['title'] = instance.ldap_user.attrs.get('title')
data['department'] = instance.ldap_user.attrs.get('departmentNumber')
data['cost_center'] = instance.ldap_user.attrs.get('businessCategory')
data['location'] = instance.ldap_user.attrs.get('physicalDeliveryOfficeName')
data['employee_id'] = instance.ldap_user.attrs.get('employeeNumber')
data['employee_type'] = instance.ldap_user.attrs.get('employeeType')
data['company'] = instance.ldap_user.attrs.get('company')
data['description'] = instance.ldap_user.attrs.get('description')
data['phone_no'] = instance.ldap_user.attrs.get('mobile')
print(instance.ldap_user)
for key, value in data.items():
if value:
setattr(profile, key, value[0].encode('utf-8').decode())
profile.save()

Now Here we can populate User Profile Data using populate_user_profile function which is provided by Django LDAP

populate_user_profile()

This function basically handles the user profile data of already authenticated users in the past .As I mentioned above as well User population is done whenever the users logged in first time in the database of Django rest of the time it basically updates its from LDAP.

So first if checks that condition if User is existing in our Django database if it does than in the try catch statement we are basically trying to get the user profile if it is there in our database if it is there than we will update the profile data from our LDAP object which contains all the data attributes of profile data from Jumpcloud LDAP . else in the except statement basically we are creating the new User Profile with help of User.

At the end of this Signal if the User don’t exists in our data base than User is populated.

Now comes the second function which handles when the User is logging For the first time and we don’t have any records in the past. So with the help of first signal it is definitely getting populated.

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):

Here I am using Django signal to notify me that User has been created Now its time to populate the User profile

if created:
profile = UserProfile.objects.create(user=instance)

Here if created is basically the flag which tells me if the User is created successfully or not and the second line is basically creating the user profile from the instance.

now here inside instance we have LDAP object as well so we are going to exploit that to get the profile data as well and save it to our database.

If you don’t mention the second signal you will not be able to save the profile data when the user login for the first time It will take second time login to save the profile data.

Now lastly we have to mentioned this signal in our apps.py

from django.apps import AppConfig

class ConnectionConfig(AppConfig):
name = 'connection'

def ready(self):
import connection.signals

(Video) What is single sign on (sso) | How sso works with saml | SAML authentication with AD (2021)

Now this signals.py is not the cleaner code to be used. If there is any better way to implement this please do share. With this setting you will be able to use Jumpcloud LDAP in your application you can use it for your own org Solutions.

Next Time I will try to cover SAML SSO with Jumpcloud.

References :- Below are some of the code references used in this article

  • For Login System and its code I have used this link
  • For populating User Profile in LDAP I have referenced this link

Videos

1. Keycloak: SSO SAML
(Łukasz Budnik)
2. Adding Microsoft Authentication to Flask Python Web Application
(Vincent Stevenson)
3. How to update any third-party WordPress plugin user profile fields with the information from LDAP ?
(miniOrange)
4. How to login into WordPress site through ultimate member login page using LDAP credentials?
(miniOrange)
5. Защита ПО (и типа того): Аутентификация - база пользователей ldap + API
(Denis Kuznetsov)
6. 5. LDAP Node : Create LDAP Connection in Node js(LDAP Authentication)
(Talented Developer)
Top Articles
Latest Posts
Article information

Author: The Hon. Margery Christiansen

Last Updated: 04/17/2023

Views: 6229

Rating: 5 / 5 (70 voted)

Reviews: 85% of readers found this page helpful

Author information

Name: The Hon. Margery Christiansen

Birthday: 2000-07-07

Address: 5050 Breitenberg Knoll, New Robert, MI 45409

Phone: +2556892639372

Job: Investor Mining Engineer

Hobby: Sketching, Cosplaying, Glassblowing, Genealogy, Crocheting, Archery, Skateboarding

Introduction: My name is The Hon. Margery Christiansen, I am a bright, adorable, precious, inexpensive, gorgeous, comfortable, happy person who loves writing and wants to share my knowledge and understanding with you.