I'd like to setup an LDAP Authentication Backend in Django, and I've already used ldap3 to confirm a bind, with success.
I'm now realising that writing a class for my LDAP Backend with just ldap3 is not so straightforward, and that installing
django_auth_ldap could be another route to explore.
I've tested already some code to create a bind to the LDAP "server", and then perform a simple search. All okay. This method I tested is outside of my Django framework.
When implementing the same method into my Django framework, I encounter an issue when the method gets called. Because of the print statements, I know the view is getting called as exptected, but the part of the code whereby the first "if" statement should be executed, does not get called unless I change from "POST"
to "GET". But then this seems to create the next issue (when I set to "GET", so to force the next lines to be executed), because I'd like that a login page then gets called, where I can then input my LDAP credentials,
ultimately confirming the LDAP connection. Here is my code:
views.py
def login_ldap(request):
LDAP_SERVER = '10.222.4.88'
searchFilter='random'
print (request)
print (LDAP_SERVER)
print ("request.method:{0}".format(request.method))
if request.method == "GET":
print ("if statement executed")
username = request.GET['username']
print ("U:{0}".format(username))
password = request.GET['password']
print ("P:{0}".format(password))
# Define the server and bind_dn
server = Server(LDAP_SERVER, get_info=ALL)
bind_dn = 'cn={0}, ou=Prod, ou=Extern, ou=User, ou=ABC, dc=DEF, dc=com'.format(username)
# Define the Connection
conn = Connection(server, bind_dn, password, auto_bind=True) # Use raise_exceptions=True for exceptions
print ("search: {0}",format(conn.search))
print ("conn: {0}",format(conn))
conn.start_tls() #Session now on a secure channel. See output from following print statement and "tls started"
print ("conn_tls: {0}",format(conn))
d = conn.extend.standard.who_am_i()
print (d)
#print ("Server Info: {0}",format(server.info))
conn.open()
conn.bind()
# The LDAP search base for looking up users.
LDAP_AUTH_SEARCH_BASE = "ou=ABC, dc=DEF, dc=com"
if conn.bind():
conn.search(
search_base=LDAP_AUTH_SEARCH_BASE,
search_filter= '(cn={})'.format(searchFilter), # This is the user being searched for
search_scope=SUBTREE # BASE & LEVEL also possible settings
)
entry = conn.entries[0]
res = conn.bind()
print (res)
return render(request, 'search_page.html', {'entry':entry})
The error message received on my webpage:
MultiValueDictKeyError at /login_ldap/
"'username'"
Request Method: GET
Request URL: http://127.0.0.1:8000/login_ldap/
Django Version: 1.11
Exception Type: MultiValueDictKeyError
Exception Value:
"'username'"
I assume this is related to the GET and no longer the POST method. Why is the request.method being automatically set to GET and not POST when implementing
this in Django? Is this class for the authentication in the correct location, in the views.py file or should there be a separate file for this?
What should be included in the settings.py exactly (related to BACKEND_AUTH)?
EDIT:
views.py
def login_view(request):
if request.POST:
username = request.POST['username']
print ("U:{0}".format(username))
password = request.POST['password']
print ("P:{0}".format(password))
user = authenticate(username=username, password=password)
print (user)
if user is not None:
if user.is_active:
login(request, user)
return redirect('index')
else:
messages.error(request, "User is not active in Database")
else:
print ("Please check your credentials!")
messages.error(request, "Please check your username and password!")
return render(request, 'login.html')
index.html
<div class="row">
<div class="col-lg-3"></div>
<div class="col-lg-6"><H1>This is the public page, Please Login</H1></div>
</div>
urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^login_ldap/$', login_ldap, name='login_ldap'),
url(r'^login/$', login_view, name='login'),
url(r'^logout/$', logout_view, name='logout'),
url(r'^change_password/$', change_password, name='change_password'),
url(r'^$', index, name='index'),
]
settings.py
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)
search_page.html
{% load static %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link href='{% static "login.css" %}' rel="stylesheet">
<div class="wrapper">
<form method='post' action="" class="form-signin">{% csrf_token %}
<h2 class="form-signin-heading">Please login</h2>
<input type="text" class="form-control" name="username" placeholder="Username" required="" autofocus=""/>
<br>
<input type="password" class="form-control" name="password" placeholder="Password" required=""/>
<br>
<button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
</form>
</div>
You can use get() function to get the data.
if request.method == "GET":
print ("if statement executed")
username = request.GET.get('username')
For more reference you can see here,
MultiValueDictKeyError in Django
And about POST method you may require to import the csr_exempt and use the decorator before your view.
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
def myView(request):
Related
If I'm on foo.html (any view/template) and the session times out, and then I click on a link to bar.html (any other view), I'm taken to the login page. After successful authentication, I am redirected to bar.html. I want it to always redirect to home.html. My settings.py has LOGIN_REDIRECT_URL = 'home'.
Users are prompted to login after the sessions expires because all my views require the user to be logged in. In CBV I use:
#method_decorator([login_required], name='dispatch')
class QuestionListView(PermissionRequiredMixin, ListView):
In functional views I have a decorator:
# login_required
def home(request):
I am using the django auth and have not overridden the login view. My login.html file contains:
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<a class="button secondaryAction" href="{% url 'password_reset' %}">Forgot Password?</a>
<button type="submit" class="btn btn-secondary mt-2 pl-4">Log In</button>
</form>
Looking at the django code, I think the answer lies in django.contrib.auth.views:
class RedirectURLMixin:
next_page = None
redirect_field_name = REDIRECT_FIELD_NAME
success_url_allowed_hosts = set()
def get_success_url(self):
return self.get_redirect_url() or self.get_default_redirect_url()
def get_redirect_url(self):
"""Return the user-originating redirect URL if it's safe."""
redirect_to = self.request.POST.get(
self.redirect_field_name, self.request.GET.get(self.redirect_field_name)
)
url_is_safe = url_has_allowed_host_and_scheme(
url=redirect_to,
allowed_hosts=self.get_success_url_allowed_hosts(),
require_https=self.request.is_secure(),
)
return redirect_to if url_is_safe else ""
def get_success_url_allowed_hosts(self):
return {self.request.get_host(), *self.success_url_allowed_hosts}
def get_default_redirect_url(self):
"""Return the default redirect URL."""
if self.next_page:
return resolve_url(self.next_page)
raise ImproperlyConfigured("No URL to redirect to. Provide a next_page.")
My guess is that def get_default_redirect is happening because I click on a link (the next_page) which triggers the login (because of the session time out).
The only reason I want to have all logins go to the home page is because I'm temporarily putting an announcement banner on the home page. It's possible for a user to not see the banner if they never go to the home page by simply logging in using the method described here.
So, i want my website's content only to be visible to the registered users, so i have put "LoginRequiredMixin" amd "#login_required" tags on most of my views except login and register views. Now, for registration, i want to give the registrants a choice before registering (whether they are current university students or alumni/recent graduates), this is how i am doing this:
class choice(View):
template = "network/choice.html"
def get(self, request):
form = ChoiceForm()
return render(request, self.template, {
"form": form,
"message": "Are you a curent student or an alumni/about to graduate"
})
def post(self, request):
form = ChoiceForm(request.POST)
if form.is_valid():
current = form.cleaned_data["current"]
if current:
return HttpResponseRedirect('accounts/register/current')
else:
return HttpResponseRedirect('accounts/register/alum')
where ChoiceForm just contains a boolean field, and "register/str:type" is my registration URL.
But after i submit the Choice Form:
<form action="{% url 'network:choice' %}" method="post">
{% csrf_token %}
{{ form|crispy }}
<br>
<input type="submit" value="Proceed"
</form>
the url to which i am taken to is:
/accounts/login/?next=/register/current
(i have not included any authentication check on the registration view, that won't make any sense lol)
Where i might have gone wrong is:
because i want anyone truing to access a restricted page to be redirected to the login page, i have defined my Urlpattern as follows:
path('accounts/login/', views.login_view.as_view(), name="login_view"),
path('accounts/register/<str:type>', views.register.as_view(), name="register"),
where 'accounts/login' is the path which django redirects to with the login_required tag. Did i do something wrong here?
This is my register view, although i am pretty sure that's not where the problem is as this view isn't even loading up even if i type in the url 'register/current'. I am still being redirected to accounts/login/?next=/register/current
Urlpatterns:
path('', views.index, name='index'),
path('new/<str:type>', views.new_page.as_view(), name="new"),
path('<str:type>/<str:name>', views.page.as_view(), name="page"),
path('logout', views.logout_view, name="logout_view"),
path('accounts/login/', views.login_view.as_view(), name="login_view"),
path('accounts/register/<str:type>', views.register.as_view(), name="register"),
path('choice', views.choice.as_view(), name="choice"),
I am completely new to using JWT authentication in Django and don't know how to implement tokens within each template of my application. I am using JWT (JSON Web Tokens) instead of sessions.
Scenario:
I have a login form that uses AJAX where after clicking the submit button, it points to my API and takes the credentials of a registered user. If authenticated, I intend to use JWT authentication and access the dashboard page, and include those tokens within the page (instead of using sessions). However, I am not redirecting to the specified URL after logging in. Furthermore, I have used Simple-JWT in order to generate tokens but don't know how to include them in my template (AJAX call) in order to access an API. I have tried the following:
views.py:
I have created a login form that doesnt access the values.
Instead, I have used AJAX in my template in order to point it towards the LoginAPI below:
def login(request):
if request.method == 'POST':
login_form = LoginForm(request.POST)
if login_form.is_valid():
pass #not doing anything here since all I need is a form for logging in.
else:
login_form = LoginForm()
return render(request, "users/login.html", {"login_form" : login_form})
#Login API that gets called after clicking the form submit button.
class LoginAPI(APIView):
permission_classes = [AllowAny]
def post(self, request, format = None):
username = request.data['username']
password = request.data['password']
new_user = authenticate(username = username, password = password)
if new_user is not None:
url = 'http://localhost:8000/api/token/' #need to pass in the credentials in order to access the JWT tokens.
values = {
'username' : username,
'password' : password
}
r = requests.post(url, data = values)
token_data = r.json()
return Response(token_data) #should return the JSON tokens to AJAX in success function.
else:
return Response({"status" : "Denied."}, status=status.HTTP_400_BAD_REQUEST)
forms.py:
class LoginForm(forms.Form):
username = forms.CharField()
password = forms.CharField(widget=forms.PasswordInput)
urls.py:
from users import views as users_views
from django.conf import settings
from django.conf.urls.static import static
from users.views import LoginAPI
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path('admin/', admin.site.urls),
path('login_api/', LoginAPI.as_view(), name = 'login_api'),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('login/', user_views.login, name = 'login'),
]
login.html:
{% extends 'users/base.html' %}
{% load static %}
{% block javascript %}
<script src='{% static "users/js/main.js" %}'></script>
{% endblock %}
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
{{ login_form|crispy }}
<div class="form-group">
<button class="btn btn-outline-info" type="submit" id='login'> Login </button>
</div>
</form>
</div>
{% endblock content %}
main.js (the file that contains the AJAX function):
$('#login').click(function () {
var username = $('#username').val();
var password = $('#password').val();
$.ajax({
cache: false,
method: 'POST',
url: "/login_api/", #After click, the URL should point towards the LoginAPI in views.py
data: {username:username, password: password},
datatype: 'json',
success: function(data) {
console.log(data.access);
localStorage.setItem('access', data.access);
window.location.href = 'http://127.0.0.1:8000/punch_funcs'; #the template I wish to go to after successful logging in.
},
When testing the login form, I found out that I am being redirected to my template 'punch_funcs'. But the URL is the same as my login page which means that whenever I refresh the page, I am taken back to my login template:
'http://localhost:8000/login' this should be 'http://localhost:8000/punch_funcs' instead.
Also, the tokens have been generated successfully, but how can I place this within my 'punch_funcs' template in order to access several APIs (not listed in the code above)?
Any help is highly appreciated. Thank you very much.
i tried using the next parameter but it doesnt affect anything
here is the template
<div class="col-md-12 text-center">
<img src = "{% static 'images/fbconnect.png' %}" height="45px" >
<img src = "{% static 'images/g_login.png' %}" height="45px" >
</div>
here is the view
if request.POST.get('action') == 'login':
username_email = request.POST.get('user_email')
password = request.POST.get('password')
try:
the_user = User.objects.get(username=username_email)
except:
the_user = User.objects.get(email=username_email)
if the_user is not None:
user = authenticate(username=username_email , password=password)
if user is not None:
login(request , user)
return HttpResponse(json.dumps({'status' : 'True'}))
Redirect param doesn't work in you example because you are not using it in your view, you are just returning HttpResponse.
You should return HttpResponseRedirect with URL where you want to redirect your user. Please see how allauth implements this here:
https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py
Specifically, see these two lines in RedirectAuthenticatedUserMixin, where redirect URL is fetched and redirected:
redirect_to = self.get_authenticated_redirect_url()
response = HttpResponseRedirect(redirect_to)
I am trying to build an admin action 'download_selected' which will download selected models. When the action is selected, I redirect to an intermediate page so that users can select a download format. When a user selects a download format and clicks on 'download', it downloads the file. But stays on the same intermediate page. How do I redirect it back to change form admin page? This redirection that I want is similar to django 'download selected file' default admin action. Thanks.
Here is my code.
admin.py
class SelectDownloadFormatForm(forms.Form):
DOWNLOAD_TYPE_CHOICES=[('csv','csv'),
('json', 'json'),
('xml','xml')]
_selected_action = forms.CharField(widget=forms.MultipleHiddenInput)
download_type = forms.ChoiceField(label=_('Select a Download type'), choices=DOWNLOAD_TYPE_CHOICES, widget=forms.RadioSelect())
def download_selected(self, request, queryset):
import csv
from django.http import HttpResponse, HttpResponseRedirect
import StringIO
form = None
if 'download' in request.POST:
form = self.SelectDownloadFormatForm(request.POST)
if form.is_valid():
dtype = form.cleaned_data['download_type']
print dtype
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="export.csv"'
writer = csv.writer(response)
writer.writerow(['id', 'name', 'qid' ,'label', 'name', 'field'])
count = 0
for s in queryset:
questions_query = ParentModel.objects.filter(parent_form_id = s.id)
for q in questions_query:
writer.writerow([s.id, s.name, q.id, q.label, q.name, q.field])
count += 1
plural = ''
if count != 1:
plural = 's'
self.message_user(request, "Successfully downloaded %d survey response%s in %s format" % (count, plural, dtype))
return response
if not form:
form = self.SelectDownloadFormatForm(initial={'_selected_action': request.POST.getlist(admin.ACTION_CHECKBOX_NAME)})
return render(request,'admin/download_type.html', {'items': queryset,
'download_type_form': form,
})
download_selected.short_description = "Download selected forms"
download_type.html
{% extends "admin/base_site.html" %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
{{ download_type_form }}
<p>Following survey will be downloaded with corresponding responses:</p>
<ul>{{ items|unordered_list }}</ul>
<input type="hidden" name="action" value="download_selected" />
<input type="submit" name="download" value="Download" />
</form>
{% endblock %}
I added an extra button to go back
Go Back
You'll need javascript for the redirect.
You can use jQuery File Download so you can do:
$.fileDownload('/url/to/download').done(function {
// redirect
})
Not sure if you can combine it with a form post.