I am currently working on implementing Duo Two-Factor Authentication into my django project. Currently it looks like django-duo-auth is the best package for this. I installed the package and went through the basic instructions on their README:
https://github.com/Elemnir/django-duo-auth/blob/master/README.rst
However this has caused my project to continuously redirect to a nonexistent subdirectory of 'duo' which is what I named the path. For example my app is loaded in XX.XX.XX.XX:YYYY Going to that url auto redirects the page to:
http://XX.XX.XX.XX:YYYY/duo/login/?next=/
Or, XX.XX.XX.XX:YYYY/admin auto redirects to:
http://XX.XX.XX.XX:YYYY/duo/login/?next=/admin
This simply will lead to django's generic base.html that duo_auth_form.html extends
Here are some snippets of relevant code, though it doesn't differ to much from the package's README suggestions
/urls.py
urlpatterns = [
...
path('admin/', admin.site.urls),
path('duo/', include('duo_auth.urls')),
]
/settings.py
INSTALLED_APPS = [
...
'duo_auth',
]
MIDDLEWARE = [
...
'duo_auth.middleware.DuoAuthMiddleware',
]
DUO_CONFIG = {
'DEFAULT': {
'HOST': '<api-host-url>',
'IKEY': '<integration_key>',
'AKEY': '<app_secret_key>',
'SKEY': '<secret_key>',
'FIRST_STAGE_BACKENDS': [
'django.contrib.auth.backends.ModelBackend',
]
}
}
The only difference anywhere from the read me is a slight redirection in the sample do_auth_form.html
where I extend to a subdirectory of my templates i.e. {% extends "dir\base.html" %} at the top of the file.
It appears like this package is fairly new and there isn't a lot of forums for issues so I figured it would be best to ask here. Any help would be appreciated!
I believe this is actually the django-duo-auth package working as intended. The way the middleware works is that after adding it to your project, any authenticated user who was authenticated using one of the FIRST_STAGE_BACKENDS will be checked to see if they've been authenticated with Duo as well, if not, they'll be redirected to the Duo login page at duo/login/ and prompted to complete a Duo authentication, similar to how Django's built-in #login_required decorator redirects an anonymous user to accounts/login/ to log in.
If you have users you don't want to force a second factor for, I would recommend creating a subclass of the ModelBackend that only authenticates for those users. Similar to the approach described in this issue, but inverting the logic:
https://github.com/Elemnir/django-duo-auth/issues/1
Related
I have been trying to setup password reset functionality in DRF using django-rest-auth. Earlier I was getting error TemplateDoesNotExist:registration/password_reset_email.html which I resolved by adding the following code
serializer.py
from rest_auth.serializers import PasswordResetSerializer
from allauth.account.forms import ResetPasswordForm
class PasswordSerializer(PasswordResetSerializer):
password_reset_form_class = ResetPasswordForm
settings.py
REST_AUTH_SERIALIZERS = {
'PASSWORD_RESET_SERIALIZER': 'api.serializers.PasswordSerializer',
}
However, Now I am getting into another issue - "NoReverseMatch: Reverse for 'account_reset_password_from_key' not found. 'account_reset_password_from_key' is not a valid view function or pattern name.". And haven't found any solution or workaround for this.
Any help would be appreciated.
So, finally I got the password reset functionality working. Here is how it goes -
We just need one URL in our urls.py -
urlpatterns = [
url(r'^account/', include('allauth.urls')),
url(r'^rest-auth/', include('rest_auth.urls')),
# This is the only URL required for BASIC password reset functionality.
# This URL creates the confirmation link which is sent via e-mail. All of the rest
# password reset features get their reverse lookup via django-allauth and django-rest-auth.
url(r'^password-reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', TemplateView.as_view(), name='password_reset_confirm'),
url(r'^rest-auth/registration/account-confirm-email/(?P<key>[-:\w]+)/$', allauthemailconfirmation,
name="account_confirm_email"),
url(r'^rest-auth/registration/', include('rest_auth.registration.urls'), name='account_signup'),
]
Using this URL configuration raised TemplateDoesNotExist at /api/rest-auth/password/reset/ error first. After a lot of debugging, I found that the issue was raised for the template - registration/password_reset_email.html which resides under the Django Admin's template directory. This happened due to another Django app that I was using and it had disabled the django admin app.
So, adding 'django.contrib.admin' under INSTALLED_APPS and removing the serializers resolved the issue.
I hope this resolves issue for others as well.
PS: Debugger is your best friend. ;)
Currently I've extended the standard Django user class by linking it to a profile class. The profile model is partially used for account activation through email, and includes an account_activated boolean. I'd like to require that accounts be activated in order to login in any way, including using the login button that is added to the django rest framework browsable api by adding
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
from the tutorial here http://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/
to the project urls. Is there any way to do this?
I think this is a classical example where permissions work fine. What you need to do:
Create a permission class that will check for a user if her account
is activated. For more see drf documentation;
Add your new permission class to the set of default permissions in settings.py in drf settings:
settings.py
REST_FRAMEWORK = {
...,
'DEFAULT_PERMISSION_CLASSES': [
...,
'your.permission.class',
...,
],
...
}
This is so simple, yet it seems that its not provided.
Basically, if my site is...
http://www.example.com
http://127.0.0.1:8000
Or a non-root install like
http://www.example.com/ye-ol-django/
http://127.0.0.1:8000/ye-ol-django/
...I would think django would know this and have a constant available in templates.
The solutions I find involve:
Set it up in settings.py with SITE_URL =
Reference settings.py in a view.
Finally access it in the template with {{ SITE_URL }} or something.
Not very D.R.Y.
Not to sound spoiled, but doesn't django provide the {{ GET_ME_THE_ROOT_URL }} reference?
Sorry, django has trained me to expect goodies like this.
Just sayin' if I was writing a framework that would be the first thing I do, besides putting a small fridge beside my desk full of hotpockets and a microwave a safe but close distance away.
Ha! Nice question.
Let's break down your problem. You want some data to be available across all the templates available in your project. And also, you want to provide the value once and not repeat it across views.
Template Context Processors is the thing you are looking for.
In your settings.py file, add a new context_processor to the list of TEMPLATE_CONTEXT_PROCESSORS.
TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
"django.core.context_processors.media",
"django.core.context_processors.request",
"django.contrib.messages.context_processors.messages",
"your_app.context_processors.root_url"
)
Then, inside your_app, create a file named context_processors.py. This file will contain the following code.
from django.conf import settings
def root_url(request):
"""
Pass your root_url from the settings.py
"""
return {'SITE_URL': settings.ROOT_URL_YOU_WANT_TO_MENTION}
And, in each of your templates, you'll have a {{SITE_URL}} present in the context depending on the value you provide to ROOT_URL_YOU_WANT_TO_MENTION in your settings.py file.
Django sure spoils everyone. But provides the mechanisms to keep you spoilt.
Hope this solves your problem.
If you're rendering the template from a request, you can just name your root view, then refer to it with the url tag:
In your root urls.py:
url(r'^$', HomePageView.as_view(), name='home'),
In template.html:
click here
More good info over in the django docs: https://docs.djangoproject.com/en/1.8/ref/templates/builtins/#url
I only want to allow people to sign up or log in with their social account. I have the social sign up and log in working, but I cant figure out how to disable the local sign up.
I've read the docs and this sounds close to what I want
ACCOUNT_FORMS (={})
Used to override forms, for example: {‘login’: ‘myapp.forms.LoginForm’}
It seems like I can make a new sign up form and only include the social log in link, but I was hoping there is any easier way that I'm overlooking. I'm still new to this all so I tend to miss the obvious a lot still.
I also tried changing the code below to False, but that disabled social sign up as well.
allauth.account.adapter.py
def is_open_for_signup(self, request):
"""
Checks whether or not the site is open for signups.
Next to simply returning True/False you can also intervene the
regular flow by raising an ImmediateHttpResponse
"""
return True
Change templates and urlpatterns
You would have to change both the templates (login, signup, etc.) and urlpatterns provided by allauth by default, which relate to the classic signup/login flow using email.
Changing/reducing the available routes via the urlpatterns ensures that only the routes are available that should be there. HTTP error 404 is then shown for any attempt to hack into existing allauth default functionality (related to email) if you do it right.
Changing the templates can ensure that the user interface does not provide what is related to email-based authentication.
No easy option available
Unfortunately, as of today there is no easy switch or setting to simply disable email-based signup and authentication with django-allauth. More details may be on GitHub in future, see:
Issue #1227 ("Social only: disable all local account handling by means of a simple setting")
Issue #345 ("How to disable form login/signup?")
Sample: urls.py
An urls.py like this will work with the current django-allauth (v0.30.0) on Django 1.10:
from django.conf.urls import include, url
from allauth.account.views import confirm_email, login, logout
from allauth.compat import importlib
from allauth.socialaccount import providers
providers_urlpatterns = []
for provider in providers.registry.get_list():
prov_mod = importlib.import_module(provider.get_package() + '.urls')
providers_urlpatterns += getattr(prov_mod, 'urlpatterns', [])
urlpatterns = [
url(r'^auth/', include(providers_urlpatterns)),
url(r'^confirm-email/(?P<key>[-:\w]+)/$', confirm_email, name='account_confirm_email'),
url(r'^login/$', login, name='account_login'),
url(r'^logout/$', logout, name='account_logout'),
url(r'^signup/$', login, name='account_signup'), # disable email signup
]
The solution wasn't what I originally thought. The much easier way to do this, instead of changing the forms, was to change the template and just remove any other options in that template.
My page now correctly only shows social auth and I am happy.
If anyone has a better or more secure answer I'd be open to it. Being new still, I don't know if this is the best solution, but for now it seems great and will mark as answered.
Ok, here is the thing. If you are not using any social account to link to your users, then it's very simple to finish the task you described by simply only include urls you need. However, if you need to use social account to link your users, then you have to include all urls because most third party application will not certify the request from your app. they only accept request from allauth.
from django.urls import path, re_path
from allauth.account import views as accountviews
urlpatterns = [
path('admin/', admin.site.urls),
# remember to comment out the following line since it will
# include all urls from allauth lib
# path('accounts/', include('allauth.urls'))
]
# assume you only want singup page and login page from allauth
urlpatterns += [path("acc/signup/", accountviews.signup, name="account_signup"),
path("acc/login/", accountviews.login, name="account_login")
]
For example I can point the url '^/accounts/password/reset/$' to django.contrib.auth.views.password_reset with my template filename in the context but I think need to send more context details.
I need to know exactly what context to add for each of the password reset and change views.
If you take a look at the sources for django.contrib.auth.views.password_reset you'll see that it uses RequestContext. The upshot is, you can use Context Processors to modify the context which may allow you to inject the information that you need.
The b-list has a good introduction to context processors.
Edit (I seem to have been confused about what the actual question was):
You'll notice that password_reset takes a named parameter called template_name:
def password_reset(request, is_admin_site=False,
template_name='registration/password_reset_form.html',
email_template_name='registration/password_reset_email.html',
password_reset_form=PasswordResetForm,
token_generator=default_token_generator,
post_reset_redirect=None):
Check password_reset for more information.
... thus, with a urls.py like:
from django.conf.urls.defaults import *
from django.contrib.auth.views import password_reset
urlpatterns = patterns('',
(r'^/accounts/password/reset/$', password_reset, {'template_name': 'my_templates/password_reset.html'}),
...
)
django.contrib.auth.views.password_reset will be called for URLs matching '/accounts/password/reset' with the keyword argument template_name = 'my_templates/password_reset.html'.
Otherwise, you don't need to provide any context as the password_reset view takes care of itself. If you want to see what context you have available, you can trigger a TemplateSyntax error and look through the stack trace find the frame with a local variable named context. If you want to modify the context then what I said above about context processors is probably the way to go.
In summary: what do you need to do to use your own template? Provide a template_name keyword argument to the view when it is called. You can supply keyword arguments to views by including a dictionary as the third member of a URL pattern tuple.
Strongly recommend this article.
I just plugged it in and it worked
http://garmoncheg.blogspot.com.au/2012/07/django-resetting-passwords-with.html
You just need to wrap the existing functions and pass in the template you want. For example:
from django.contrib.auth.views import password_reset
def my_password_reset(request, template_name='path/to/my/template'):
return password_reset(request, template_name)
To see this just have a look at the function declartion of the built in views:
http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/views.py#L74
You can do the following:
add to your urlpatterns (r'^/accounts/password/reset/$', password_reset)
put your template in '/templates/registration/password_reset_form.html'
make your app come before 'django.contrib.auth' in INSTALLED_APPS
Explanation:
When the templates are loaded, they are searched in your INSTALLED_APPS variable in settings.py .
The order is dictated by the definition's rank in INSTALLED_APPS, so since your app come before 'django.contrib.auth' your template were loaded (reference: https://docs.djangoproject.com/en/dev/ref/templates/api/#django.template.loaders.app_directories.Loader).
Motivation of approach:
I want be more dry and don't repeat for any view(defined by django) the template name (they are already defined in django)
I want a smallest url.py
Another, perhaps simpler, solution is to add your override template directory to the DIRS entry of the TEMPLATES setting in settings.py. (I think this setting is new in Django 1.8. It may have been called TEMPLATE_DIRS in previous Django versions.)
Like so:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# allow overriding templates from other installed apps
'DIRS': ['my_app/templates'],
'APP_DIRS': True,
}]
Then put your override template files under my_app/templates. So the overridden password reset template would be my_app/templates/registration/password_reset_form.html
The documentation says that there only one context variable, form.
If you're having trouble with login (which is common), the documentation says there are three context variables:
form: A Form object representing the login form. See the forms documentation for more on Form objects.
next: The URL to redirect to after successful login. This may contain a query string, too.
site_name: The name of the current Site, according to the SITE_ID setting.
I was using this two lines in the url and the template from the admin what i was changing to my need
url(r'^change-password/$', 'django.contrib.auth.views.password_change', {
'template_name': 'password_change_form.html'}, name="password-change"),
url(r'^change-password-done/$', 'django.contrib.auth.views.password_change_done', {
'template_name': 'password_change_done.html'
}, name="password-change-done")