Intro
Django version: 1.10
Python version: 3.5.2
I'm trying to implement an authentication based on a LDAP condition and I can't get my head around on how to achieve this.
My project already uses Django's built-in authentication system, which works great and looks like this:
# urls.py
from django.conf.urls import include, url
from django.contrib import admin
from django.contrib.auth import views
from coffee_app.forms import LoginForm
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'', include('coffee_app.urls')),
url(r'^login/$', views.login, {'template_name': 'login.html', 'authentication_form': LoginForm}, name='login'),
url(r'^logout/$', views.logout, {'next_page': '/login'}, name='logout'),
]
# forms.py
from django.contrib.auth.forms import AuthenticationForm
from django import forms
class LoginForm(AuthenticationForm):
username = forms.CharField(label="Username", max_length=32,
widget=forms.TextInput(attrs={
'class': 'form-control',
'name': 'username'
}))
password = forms.CharField(label="Password", max_length=20,
widget=forms.PasswordInput(attrs={
'class': 'form-control',
'name': 'password'
}))
<!--login.html (relevant part)-->
<form class="form-horizontal" action="{% url 'login' %}" method="post" id="contact_form">
{% csrf_token %}
<fieldset>
<div class="form-group">
<label class="col-md-4 control-label">{{ form.username.label_tag }}</label>
<div class="col-md-4 inputGroupContainer">
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
{{ form.username }}
</div>
</div>
</div>
<div class="form-group">
<label class="col-md-4 control-label" >{{ form.password.label_tag }}</label>
<div class="col-md-4 inputGroupContainer">
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
{{ form.password }}
</div>
</div>
</div>
<div class="form-group">
<label class="col-md-4 control-label"></label>
<div class="col-md-4 text-center">
<br>
<button value="login" type="submit" class="btn btn-warning" >
LOG IN
<span class="glyphicon glyphicon-send"></span>
</button>
</div>
</div>
</fieldset>
<input type="hidden" name="next" value="{{ next }}"/>
</form>
Problem
Now, what I'm trying to do, is to verify whether a user exists in LDAP or not, before getting to the initial Django auth:
from ldap3 import Server, Connection, ALL, NTLM
server = Server('server here', get_info=ALL, use_ssl=True)
conn = Connection(server,
user='DOMAIN\\username',
password='password',
authentication=NTLM)
print(conn.bind())
If conn.bind() returns True, I'd like to go further to Django's built-in authentication system and authenticate the user. Unfortunately, I don't know where / how to add this step in order to achieve this.
Some views look like this:
from django.shortcuts import render
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
#login_required(login_url="login/")
def home(request):
return render(request, "home.html")
#login_required(login_url="login/")
def activity_report_page(request):
return render(request, "activity_report.html")
...
And their urls:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.home, name='home'),
url(r'report$', views.activity_report_page, name='activity_report')
]
Could someone please point out where should I add the LDAP piece of code so that I can first verify if a user exist there?
PS: I didn't consider using django-auth-ldap because I don't really need a pure LDAP authentication-based system. Just a simple verification.
You want to customize authentication in Django, where you more specifically want to write an authentication backend. I assume that your project is called 'coffee_site', and you have the app 'coffee_app'. You first want to change coffee_site/settings.py, and append AUTHENTICATION_BACKENDS = ['coffee_site.auth.LDAP'] to it. After this, you want to make and edit coffee_site/auth.py. As you said in the question you want to use the default authentication, and so you should inherit from django.contrib.auth.backends.ModelBackend, you then want to make it so that if conn.bind() is not True, then you don't use the default authentication, and so you should return None. This can be implemented with:
from django.contrib.auth.backends import ModelBackend
from ldap3 import Server, Connection, ALL, NTLM
server = Server('server here', get_info=ALL, use_ssl=True)
class LDAP(ModelBackend):
def authenticate(self, *args, **kwargs):
username = kwargs.get('username')
password = kwargs.get('password')
if username is None or password is None:
return None
conn = Connection(server,
user='DOMAIN\\{}'.format(username),
password=password,
authentication=NTLM)
if not conn.bind():
return None
return super().authenticate(*args, **kwargs)
Note: I checked this works on Django's side, but I made no effort to check that the LDAP code works.
Related
I am creating a django project involving multiple types of users. For that, I have not created new User classes, rather I have just extended the model using a OneToOne relation (like we do while creating a profile model). Model in one app is like this:
class Dev(models.Model):
user = models.OneToOneField(User, on_delete = models.CASCADE)
BaseSkills = models.ManyToManyField(ProjectBaseSkills,
help_text='Adding Base skills help us to identify the best projects for you', blank=True)
PhoneNumber = models.BigIntegerField(
help_text="Don't add the country code (the user base is restricted to India for now)")
def __str__(self):
return f'{self.user.first_name} {self.user.last_name}'
and in the other app is like this:
class Hirer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
Name = models.CharField(max_length=120, default='Hirer')
PhoneNumber = models.BigIntegerField(blank=True)
This shows how there are two types of users: Devs and Hirers.
Now since there are multiple user types, I need to redirect the different users to different urls after login, so I can't just rely on the LOGIN_REDIRECT_URL settings. Also I don't want to define a login view myself, so I am using the default LoginView from django auth.
Here is the urls.py for the app where Dev user is used:
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from django.contrib.auth import views as auth_views
from . import views
urlpatterns = [
path('login/', auth_views.LoginView.as_view(template_name='dev_user/login.html'), name='dev-login'),
path('register/', views.Register, name='dev-register')
] + static(settings.MEDIA_URL, document_root =settings.MEDIA_ROOT)
And the one where Hirer user is used:
from django.urls import path
from django.contrib.auth import views as auth_views
from django.conf import settings
from django.conf.urls.static import static
from . import views
urlpatterns = [
path('login/', auth_views.LoginView.as_view(template_name='client_user/login.html'), name='client-login'),
path('register/', views.Register, name='client-register'),
] + static(settings.MEDIA_URL, document_root =settings.MEDIA_ROOT)
In the documentation (https://docs.djangoproject.com/en/3.0/topics/auth/default/#django.contrib.auth.views.LoginView) it is said that we can override the redirect url by using the redirect_field_name in GET field of the LoginView. But there's not example code or something, and I'm quite new at programming, so I don't know how to do that. There are also no examples on the net, so please someone help me out here.
I am grateful to all the people who answered. Though my original query was not solved, I did find a get around to do a login. Here's what I did for others to implement if they're stuck in a similar situation:
(views.py):
def Login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
return redirect('dev-profile')
return render(request, 'client_user/login.html')
(template):
{% extends "home/base.html" %}
{% block content %}
{% load crispy_forms_tags %}
<div class="separator-row">
<div class="col-sm-12 col-md-4">
<div class="add-padding">
<form method="POST">
<fieldset>
{% csrf_token %}
<legend><h2 class="signup-info-title">Welcome Back Hirer!</h2></legend>
<div id="div_id_username" class="form-group">
<label for="username" class=" requiredField">
Username
<span class="asteriskField">*</span>
</label>
<div class="">
<input type="text" name="username" autofocus="" autocapitalize="none" autocomplete="username" maxlength="150" class="textinput textInput form-control" required="" id="username">
</div>
</div>
<div id="div_id_password" class="form-group">
<label for="password" class=" requiredField">
Password
<span class="asteriskField">*</span>
</label>
<div class="">
<input type="password" name="password" autocomplete="current-password" class="textinput textInput form-control" required="" id="password">
</div>
</div>
</fieldset>
<button class="login-buttons" type="submit">Login</button>
<hr>
<div class="login-footers">
<p class="text-muted">Don't have an account? Sign up as a Hirer!</p>
<p class="text-muted">Not a Hirer? Login or Sign Up as a Dev</p>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
Also if you have an answer to the original query, please do answer!
You can try something like this (in your views):
#login_required
def custom_redirect(request):
if request.user.dev.filter(BaseSkills='xxx'):
return redirect('xxx')
elif request.user.dev.filter(BaseSkills='xxx'):
return redirect('xxx')
Also note that best practise is to have model field names in lowercase, using underscores instead of camelCase.
I am using the Django auth system for my app. I have made a CustomUser table using AbstractUser functionality to add some more fields.
//models.py
from django.contrib.auth.models import AbstractUser
from django.contrib.sessions.models import Session
class CustomUser(AbstractUser):
addr1= models.CharField(max_length=20)
addr2= models.CharField(max_length=20)
city= models.CharField(max_length=20)
state= models.CharField(max_length=20)
class UserSession(models.Model):
user= models.ForeignKey(settings.AUTH_USER_MODEL)
session=models.ForeignKey(Session)
//views.py
from .models import UserSession
from django.contrib.auth.signals import user_logged_in
def login(request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return HttpResponseRedirect('/Student_home.html') # Redirect to a success page.
else:
return HttpResponse("disabled account") # Return a 'disabled account' error message
else:
return HttpResponse("invalid login") # Return an 'invalid login' error message.
def logout(request):
logout(request)
return HttpResponseRedirect('/Student_home.html') # Redirect to a success page.
def user_logged_in_handler(sender,request,user, **kwargs):
UserSession.objects.get_or_create(
user= user,
session_id= request.session.session_key
)
user_logged_in.connect(user_logged_in_handler, sender=CustomUser)
def delete_user_sessions(CustomUser):
user_sessions=UserSession.objects.filter(user=CustomUser)
for user_session in user_sessions:
user_session.session.delete()
//forms.py
from django.contrib.auth.forms import AuthenticationForm
from django import forms
class LoginForm(AuthenticationForm):
username = forms.CharField(label="Username", max_length=30,
widget=forms.TextInput(attrs={'class': 'form-control', 'name': 'username'}))
password = forms.CharField(label="Password", max_length=30,
widget=forms.TextInput(attrs={'class': 'form-control', 'name': 'password'}))
//project/urls.py(the outer one)
from django.contrib.auth import views
from student.forms import LoginForm
url(r'^login/$', views.login, {'template_name': 'login.html', 'authentication_form': LoginForm}, name='login'),
url(r'^logout/$', views.logout, {'next_page': '/login'}),
//login.html
<div class="container">
<section id="content">
<form action="{% url 'login' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<h1>Login Form</h1>
<div class="imgcontainer">
<img src="{% static 'student/patient.jpg' %}" alt="Avatar" class="avatar">
</div>
<div class="username">
{{ form.username.label_tag }}
{{ form.username }}
</div>
<div class="password">
{{ form.password.label_tag }}
{{ form.password }}
</div>
<div class="submitb">
<input type="submit" value="Log In" name="mybtn">
</div>
<div class="resetb">
<input type="submit" value="Reset">
Forgot password?
</div>
<input type="hidden" name="next" value="{{ next }}" />
</form>
</section>
</div>
//settings.py
LOGIN_REDIRECT_URL = '/login/sample'
The login system and authentication system is working all fine. But here is the problem. It doesn't take any action when the user can't log in (wrong username and password). But it does gets redirected to 'sample' page which I guess is dut to the thing in settings.py. Also the user is not redirected to any page when the user is logged out although I have specified that it should be redirected to a home page. I think the control is not getting inside the 'login' and 'logout' view. Also the sessions are also not getting deleted when a user logs out as these sessions create and delete on the 'login()' and 'logout()' functions of the django auth system.
What is wrong here?
New to Django and came from Laravel where it was much easier to setup login and authentication. That is to say I am struggling getting a simple login page to work properly and have spent several nights working on it using SO, Django docs, and various tutorials. The Django documentation doesn't seem to be organized well for someone just trying to get the basics of login page, kind of overwhelming, and most of the SO answers are old that I have come across.
These two tutorials I have used and neither gets me what I am after:
Right way of adding user authentication to Django
How to Use Django's Built-in Login System
Using Python 3.5.2, Django 1.10, and PostgreSQL 9.6. In my setup, localhost:8000 is the login page. The user needs to login before they can view anything. I also have apps /home, /results, etc. but right now I am only working with /home. If they try to go to localhost:8000/home they will be routed to login doing this currently brings them to http://localhost:8000/?next=/home/. So that much is working good.
When I enter credentials, it just keeps sending me to the login screen. Going to localhost:8000 or /home just does the same thing. Doesn't seem like it is authenticating. DB credentials are correct, user credentials are correct and communicating with the database in python manage.py shell is fine.
Here is my simplified directory structure:
~/portal-client
project_dir
apps
account
templates
account
login.html
forms.py
urls.py
views.py
home
templates
home
home.html
urls.py
views.py
results
settings.py
urls.py
scripts
manage.py
static
templates
base.html
footer.html
title.html
project_dir/settings.py
# INSTALLED_APPS, TEMPLATES, STATIC, DATABASES all correct so just this line added
LOGIN_REDIRECT_URL = 'home'
project_dir/urls.py
from django.conf.urls import include, url
from django.contrib import admin
from django.contrib.auth import views as auth_views
from account.forms import LoginForm
urlpatterns = [
url(r'', include('account.urls')),
url(r'^admin/', admin.site.urls),
url(r'^documents/$', include('documents.urls')),
url(r'^help/$', include('help.urls')),
url(r'^home/$', include('home.urls')),
url(r'^results/$', include('results.urls')),
url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'),
url(r'^login/$', auth_views.login, {'template_name': 'account/login.html', 'authentication_form': LoginForm}, name='login'),
]
home/views.py
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
# Create your view here.
# The following prohibits access to the website unless authenticated
#login_required(login_url="/")
def home(request):
return render(request, 'home/home.html')
account/urls.py - EDIT
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.user_login, name='user_login'),
]
account/forms.py - EDIT
from django.contrib.auth.forms import AuthenticationForm
from django import forms
# If you don't do this you cannot use Bootstrap CSS
class LoginForm(AuthenticationForm:
username = forms.CharField(max_length=50)
password = forms.CharField(widget=forms.PasswordInput)
def clean_username(self):
username = self.cleaned_data.get("username")
def clean_password(self):
password= self.cleaned_data.get("password")
account/views.py - EDIT
from django.shortcuts import render
from django.contrib.auth import views
from .forms import LoginForm
# Create your view here.
def user_login(request):
form = LoginForm(request.POST or None)
if form.is_valid():
username = form.cleaned_data['username']
password = form.cleaned_data['password']
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
return httpResponseRedirect('/home')
else:
return httpResponseRedirect('/')
context = {
"form" : form
}
return render(request, "account/login.html", context)
account/login.html
<form class="form-horizontal" role="form" method="POST" action="{% url 'home' %}">
{% csrf_token %}
<div class="form-group">
<label for="email" class="col-md-4 control-label">Username</label>
<div class="col-md-6">
{{ form.username }}
</div>
</div>
<div class="form-group">
<label for="password" class="col-md-4 control-label">Password</label>
<div class="col-md-6">
{{ form.password }}
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<div class="checkbox">
<label>
<input type="checkbox" name="remember"> Remember Me
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-8 col-md-offset-4">
<button type="submit" class="btn btn-primary">
Login
</button>
<a class="btn btn-link" href="url('/password/reset')">
Forgot Your Password?
</a>
</div>
</div>
</form>
I think that is everything! I appreciate the help in advance! Would love to get on with putting these apps together and get past the authentication.
EDIT:
One thing I haven't read though is if I need to do anything with account/models.py file. All the customer data is in in the default auth_user table.
Also, wondering if something like django-allauth might be a good solution.
Django comes with authentication views, you should use these if possible instead of writing your own.
You already included the built-in login view in your URL patterns.
url(r'^login/$', auth_views.login, {'template_name': 'account/login.html'}, name='login'),
Therefore you don't need your user_login view, so I would remove it. I have removed your custom authentication form because it is not required. The default authentication form already has the username and password fields. You have broken the clean_ methods because you don't return a value.
Next, fix the form's action in your template. Currently you are submitting the form data to the home view. You should send it to the login view.
<form class="form-horizontal" role="form" method="POST" action="{% url 'login' %}">
Finally, I would add LOGIN_URL = 'login' to your settings, then you can simplify the code that uses login_required to the following:
#login_required
def home(request):
...
I'm building a little web server using django but I keep getting 'str' object has no attribute 'regex' in my template where it says {% url 'signup' %} and {% url 'login' %}. I guess this will be something to do with URL binding, or probably me not having imported the right module that is needed to refer to a URL by its name attribute. But I can't figure a way around this. Thanks in advance.
Template
{% include "header.html" %}
<div class="container">
<div class="page-header"><h3>로그인</h3></div>
<form method="post" action="{% url 'login' %}" role="login">
{% csrf_token %}
<div class="form-group">
<label>아이디</label>
<input type="text" name="username" placeholder="아이디를 입력하세요" class="form-control" />
</div>
<div class="form-group">
<label>비밀번호</label>
<input type="password" name="password" placeholder=" 암호를 입력하세요" class="form-control" />
<input type="hidden" name="next" value="/" />
</div>
<div class="form-group">
<div class="btn-group pull-right">
<input type="submit" value="로그인" class="btn btn-primary"/>
가입하기
</div>
</div>
</form>
</div>
{% include "footer.html" %}
Views.py
from django.shortcuts import render, redirect
from django.http import HttpResponse, HttpResponseRedirect
from django.contrib.auth import logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from docx import Document
from exam.models import *
def login(request):
return render(request, 'login.html')
def signup(request):
try:
if request.session["error"]:
error_message = request.session["error"]
del request.session["error"]
except KeyError:
error_message = None
context = {
"error_message" : error_message
}
return render(request, "signup.html", context)
def signup_submit(request):
try:
username = request.POST["username"].strip()
password = request.POST["password"].strip()
password_confirm = request.POST["password_confirm"].strip()
full_name = request.POST["full_name"].strip()
student_num = request.POST["student_num"].strip()
if username and password and password_confirm and full_name and student_num:
user = User(username=username, full_name=full_name, student_num=student_num)
user.set_password(password)
user.save()
return redirect("index")
except KeyError:
request.session["error"] = "올바른 요청이 아닙니다."
return redirect("signup")
else:
request.session["error"] = "입력한 정보가 올바르지 않습니다."
return redirect("signup")
URLS.py
from django.conf.urls import include, url, patterns
from django.contrib import admin
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'^$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}, name='login'),
url(r'^signup/$', 'exam.views.signup', name='signup'),
url(r'^signup/submit/$', 'exam.views.signup_submit', name='signup_submit'),
Error Traceback:
AttributeError at /
'str' object has no attribute 'regex'
Request Method: GET
Request URL: http://192.168.56.101:8000/
Django Version: 1.7.6
Exception Type: AttributeError
Exception Value:
'str' object has no attribute 'regex'
Exception Location: /home/web/venv/lib/python3.4/site-packages/django/core/urlresolvers.py in _populate, line 282
Python Executable: /home/web/venv/bin/python
Forbidden (403) CSRF verification failed.
Request aborted. Reason given for failure: CSRF cookie not set.
I get the above error and there are a few solutions out there, but not for class-based views. What should def get_context_data(self, **kwargs): be returning? Below the message, it suggests 4 solutions, and the following suggestion catches my attention. So perhaps I should be returning a RequestContext somehow?
The view function uses RequestContext for the template, instead of Context.
I'm already using {% csrf_token %}, cookies are enabled and I have it included in the middleware. So I think I may be returning the wrong thing but all the other examples around here use function views.
My template snippet:
{% if not user.is_authenticated %}
<form id="login" method="post" action="login">{% csrf_token %}
<input type="text" name="username" value="" placeholder="Email">
<input type="password" name="password" value="" placeholder="Password">
<input type="submit" name="commit" value="Login">
</form>
{% elif user.is_authenticated %}
<p>Welcome, {{ user.get_displayname }}.</p>
{% endif %}
My urls.py:
from django.conf.urls import patterns, include, url
from mainapp.views import Index, LoginResponse
from django.contrib import admin
admin.autodiscover()
from mainapp import views
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'^$', Index.as_view()),
url(r'^login$', LoginResponse.as_view()),
)
My LoginResponse class view:
class LoginResponse(TemplateView):
template_name = 'index.html'
def get_context_data(self, **kwargs):
context = super(LoginResponse, self).get_context_data(**kwargs)
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
return context
For CSRF verification there is no difference between function based and class based views. This verification is done at middleware level.
So show your template and urls.py please.