django-two-factor-auth can't access admin site - django

I am using django-two-factor-auth for a webapp. I cannot access the admin page.
I know I am entering the correct credentials. When I input incorrect credentials, I get an appropriate error message.
When I input the correct credentials, the page simply reloads with this URL:
http://localhost:8080/account/login/?next=/inveskore/
These are my settings related to two_factor:
LOGIN_URL = 'two_factor:login'
LOGIN_REDIRECT_URL = '/inveskore'
TWO_FACTOR_SMS_GATEWAY = 'two_factor.gateways.twilio.gateway.Twilio'
This is the associated URL path:
path('admin/', admin.site.urls),
According to this, it results from the admin user not having 2FA set.
So, how do you set 2FA for the admin user if you can't access the site?
EDIT:
I took down the 2FA login requirements for the site and then added a phone device. No luck.

I recently ran into this scenario and created this solution based on a comment there:
https://github.com/Bouke/django-two-factor-auth/issues/219#issuecomment-494382380
I subclassed AdminSiteOTPRequired and then specified it as the admin class to use
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.views import redirect_to_login
from django.http import HttpResponseRedirect
from django.shortcuts import resolve_url
from django.urls import reverse
from django.utils.http import is_safe_url
from two_factor.admin import AdminSiteOTPRequired, AdminSiteOTPRequiredMixin
class AdminSiteOTPRequiredMixinRedirSetup(AdminSiteOTPRequired):
def login(self, request, extra_context=None):
redirect_to = request.POST.get(
REDIRECT_FIELD_NAME, request.GET.get(REDIRECT_FIELD_NAME)
)
# For users not yet verified the AdminSiteOTPRequired.has_permission
# will fail. So use the standard admin has_permission check:
# (is_active and is_staff) and then check for verification.
# Go to index if they pass, otherwise make them setup OTP device.
if request.method == "GET" and super(
AdminSiteOTPRequiredMixin, self
).has_permission(request):
# Already logged-in and verified by OTP
if request.user.is_verified():
# User has permission
index_path = reverse("admin:index", current_app=self.name)
else:
# User has permission but no OTP set:
index_path = reverse("two_factor:setup", current_app=self.name)
return HttpResponseRedirect(index_path)
if not redirect_to or not is_safe_url(
url=redirect_to, allowed_hosts=[request.get_host()]
):
redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
return redirect_to_login(redirect_to)
Then in urls.py:
from django.contrib import admin
admin.site.__class__ = AdminSiteOTPRequiredMixinRedirSetup

I cannot comment yet but the answer above by #saschwarz worked like a charm with Django 2.2 and django-two-factor-auth. The only thing missing from his code is the additional import:
from django.http import HttpResponseRedirect
After that I was able to use this line:
admin.site.__class__ = AdminSiteOTPRequiredMixinRedirSetup
to quickly enforce two factor for admin users.
This is really missing from the docs for django-two-factor-auth... I looked at the documentation for quite a while and couldn't find a clear way to generate a qr code. It's only really demonstrated in the demo app, and not in a very modular way.

The solution from saschewarz didn't solve the problem in my app.
So I found another one where standard admin login is shown.
django-two-factor-auth by default patch your admin urls with function 'patch_admin' from two_factor\admin.py so you always will see your standard site login for admin login.
To solve this you can comment out 2 functions in two_factor\admin.py
def patch_admin()
and
def unpatch_admin()
And comment out in two_factor\apps.py
def ready(self)
And to use two factor authentication in admin site add this code in main urls.py (where is path to admin):
from django_otp.admin import OTPAdminSite
admin.site.__class__ = OTPAdminSite

Related

Require second login for users on Django based site

I have a Django 2.2 based project that uses a custom user model, the built-in Auth app and Django-All-Auth for user management. Almost every page on the site is behind a login and we use varying levels of permissions to determine what can be accessed a user.
So far, so good, but now I'm being asked to designate everything behind a specific part of the site as "sensitive", requiring a second login prompt using the same login credentials. What this means is that the client wants to see a login appear when they try to access anything under /top-secret/ the first time in a set time, say 30 mins, regardless of whether they're already logged in or not.
I've dug around on the internet for ideas on how to do this, but so far I've been unable to find a good solution. Has anyone here had any experience with something similar, and if so, could they point me in the right direction?
Figured out how to make this work in my situation. I debated deleting this question, but on the off chance that there's someone else out there that wants to do something similar, here's how I did it.
Create a new middleware with the following:
from allauth.account.adapter import get_adapter
from datetime import datetime
from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.shortcuts import redirect
from django.urls import reverse
from django.utils import timezone
class ExtraLoginPromptMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.user.is_authenticated:
for url in settings.EXTRA_LOGIN_URLS:
if request.path.startswith(url):
last_seen = request.session.get(settings.EXTRA_LOGIN_SESSION_VARIABLE)
if not last_seen:
last_seen = request.user.last_login
else:
last_seen = datetime.fromisoformat(last_seen)
if last_seen + relativedelta(seconds=settings.EXTRA_LOGIN_EXPIRY) < timezone.now():
# NB, this uses allauth, if you want to use django.auth, just
# call django.contrib.auth's logout instead
get_adapter(request).logout(request)
return redirect('{}?next={}'.format(reverse('account_login'), url))
else:
request.session[settings.EXTRA_LOGIN_SESSION_VARIABLE] = timezone.now().isoformat()
return self.get_response(request)
To make use of this, you'll want to add this middleware after SessionMiddleware and AuthenticationMiddleware
You'll also want to define a few variables in your settings:
EXTRA_LOGIN_URLS = ['/top-secret/', 'another-secret']
EXTRA_LOGIN_EXPIRY = 120 # How long before the "extra security" session expires
EXTRA_LOGIN_SESSION_VARIABLE = 'extra-login-last-seen'

Custom login URL in django

I am a newbie in django and I was experting different options in Django. I have created a class based view which requires user authentication to view the web page. I am using the inbuilt LoginView.
When the url pattern is specified as follows
url(r'^login/', auth_views.LoginView.as_view(),name='login'),
it is correctly redirected to login page.
But when I give
url(r'^restaurant/login/', auth_views.LoginView.as_view(),name='login'),
I get a 404 when trying to access the page that requires user authentication.
But when I manually go to that url in browser, it works perfectly fine.
Why is that? Shouldn't it both cases work?
It sounds like you need to set LOGIN_URL in your settings:
LOGIN_URL = '/restaurant/login/'
or, it's better to use the URL pattern name, then you don't have to update your settings when you change the login URL
LOGIN_URL = 'login'
Not sure if I'd fully understand your question, just try to give a stupid answer.
Django 2.1.7
use namespace&url name in settings, if you have your own login view just change admin to your url namespace and name your view as 'login'
# settings.py
LOGIN_URL = 'admin:login'
then the login_required decorator will direct you the correct login page.
from django.contrib.auth.decorators import login_required
#login_required()
def month_archive(request, year, month):
production_list = Production.objects.month_archive(year, month)
context = {'production_list': production_list}
return TemplateResponse(request, 'production/production_list.html', context)
If it's a Class Based View, add decorator to urls.py
from django.contrib.auth.decorators import login_required
urlpatterns = [
path('', login_required(views.ProductionList.as_view()), name='production-list'),
path('<int:year>/<int:month>/', views.month_archive, name='production-month'),
]

django1.8 api - how to authenticate with a key

I want to allow the django users to use a key in the api urls for authentication.
I do not have OAUTH set up yet so I guess the key could be a sesion key or a digest key.
I'm having 2 problems.
I've tried sending this request:
http://192.166.166.11:8000/api?task=search&term=115&csrf_token=s69WAIZqlCTur1XZQr72QhCc7fzqaRtM
First of all, I've tried using the csrf_token but it does not work.
It takes me to the login page.
Secondly, I do not know how to retrieve csrf_token of other users (the admin is trying to get their csrf_tokens).
My attempt:
x = User.objects.get(username='someone')
x.get_session_auth_hash()
gives me the user's authentication hash but it is a different value.
Can someone please guide me get these two problems sorted out?
You are creating a token-based authentication. You already mentioned OAUTH as one option, and I strongly recommend using one of the existing implementations like django-oauth-toolkit. However, you can also create your own quick solution to create a token-based authentication.
Disclaimer: This is for demo purposes only. Do not copy it in any existing project. It will make your application vulnerable.
First, we create an additional model handling the authentication tokens:
/auth_tokens/models.py
from django.db import models
from django.conf import settings
import string, random
def random_string(length = 64, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for x in range(length))
class AuthToken(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
token = models.CharField(max_length=64, default=random_string)
/auth_tokens/middleware.py
from auth_tokens.models import AuthToken
class AuthTokenMiddleware:
def process_request(self, request):
token = request.GET.get('auth', None)
if not token:
return
token = AuthToken.objects.get(token=token)
request.user = token.user
return request
Including the middleware into your settings.MIDDLEWARE_CLASSES should enable you to add ?token=<token> to your URL to login your users.
I ended up using token authentication:
http://www.django-rest-framework.org/api-guide/authentication/
so I'd like to share the workflow.
First, you need to do the set up. In settings.py, modify INSTALLED_APPS and add REST_FRAMEWORK as in documentation.
Then you need to run python manage.py syncdb because it needs to add some tables.
Then, you need to add some urls to urls.py to route the api.
You can create and retrieve tokens using this code:
from rest_framework.authtoken.models import Token
token = Token.objects.create(user=User.objects.get(username='john'))
print token.key
Lastly, you'll have to modify your view which depends on whether you're using a function based or class based view.
Here is a function based view I used:
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import authentication_classes, permission_classes
from rest_framework.decorators import api_view
#api_view(['GET', 'POST'])
#authentication_classes((TokenAuthentication,))
#permission_classes((IsAuthenticated,))
#login_required
def mybooks(request):
entries = Book.objects.all()
return render(request, 'mybooks.html', {'entries': entries})
Lastly, to test it out:
import requests
token = '243124c52f7583e320d043c4395bd99f63344035'
headers = {'Authorization' : 'Token {}'.format(token)}
page = requests.post('http://127.0.0.1:8000/mybooks/', headers=headers)
print page.content
Note that in my case I do not need define plain serialization since I have an advanced custom serialization and that is not the topic here.
Django doesn't provide API Keys out of the box.
Use API providers such as Tastypie to have this feature

django-allauth: Only allow users from a specific google apps domain

I am a newbie at Django. Using django-allauth I have set up single click sign in. I obtained my domain credentials ( client_id and secret_key) from google api console. But the problem is django-allauth is letting me login from any google account while I want the email addresses to be restricted to my domain ( #example.com instead of #gmail.com)
django-social-auth has the white listed domains parameter for this, how do I include this information in allauth?
I found django-allauth much easier to set up after spending hours on django-social-auth
Any help would be much appreciated.
Answering my own question-
What you want to do is stall the login after a user has been authenticated by a social account provider and before they can proceed to their profile page. You can do this with the
pre_social_login method of the DefaultSocialAccountAdapter class in allauth/socialaccount/adaptor.py
Invoked just after a user successfully authenticates via a
social provider, but before the login is actually processed
(and before the pre_social_login signal is emitted).
You can use this hook to intervene, e.g. abort the login by
raising an ImmediateHttpResponse
Why both an adapter hook and the signal? Intervening in
e.g. the flow from within a signal handler is bad -- multiple
handlers may be active and are executed in undetermined order.
Do something like
from allauth.socialaccount.adaptor import DefaultSocialAccountAdapter
class MySocialAccount(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
u = sociallogin.account.user
if not u.email.split('#')[1] == "example.com"
raise ImmediateHttpResponse(render_to_response('error.html'))
This is not an exact implementation but something like this works.
Here's an alternate solution:
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
class CustomAccountAdapter(DefaultAccountAdapter):
def is_open_for_signup(self, request):
return False # No email/password signups allowed
class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
def is_open_for_signup(self, request, sociallogin):
u = sociallogin.user
# Optionally, set as staff now as well.
# This is useful if you are using this for the Django Admin login.
# Be careful with the staff setting, as some providers don't verify
# email address, so that could be considered a security flaw.
#u.is_staff = u.email.split('#')[1] == "customdomain.com"
return u.email.split('#')[1] == "customdomain.com"
This code can live anywhere, but assuming it's in mysite/adapters.py, you'll also need the following in your settings.py:
ACCOUNT_ADAPTER = 'mysite.adapters.CustomAccountAdapter'
SOCIALACCOUNT_ADAPTER = 'mysite.adapters.CustomSocialAccountAdapter'
You could do something in the line of overriding allauth's allauth.socialaccount.forms.SignupForm and checking the domain during the signup process.
Discalmer: this is all written without testing, but something in the line of that should work.
# settings.py
# not necesarry, but it would be a smart way to go instead of hardcoding it
ALLOWED_DOMAIN = 'example.com'
.
# forms.py
from django.conf import settings
from allauth.socialaccount.forms import SignupForm
class MySignupForm(SignupForm):
def clean_email(self):
data = self.cleaned_data['email']
if data.split('#')[1].lower() == settings.ALLOWED_DOMAIN:
raise forms.ValidationError(_(u'domena!'))
return data
in your urls override allauth defaults (put this before the include of django-allauth)
# urls.py
from allauth.socialaccount.views import SignupView
from .forms import MySignupForm
urlpatterns = patterns('',
# ...
url(r"^social/signup/$", SignupView.as_view(form_class=MySignupForm), name="account_signup"),
# ...
)
I'm not sure for the "^social/signup/$", recheck that.

django multiple admin instances and locking down access to a particular instance

I have several admin instances running on a site - one for each country, that the site supports.
However, if a user logs into one admin, they are automatically able to access other instances.
I need to make the auth code aware of which admin the user has logged into and prevent access to other admin systems.
Any ideas how this can be done?
You can use middleware to check for user permissions to access certain areas of admin site. Checkout this snippet. (You might want to know more about handling custom permissions in Django.)
If you need something more universal, you can use the code example below. The idea is simple: it uses custom functions to find out about user permissions and to give an appropriate response:
#coding: utf-8
# Note that RESTRICTED_URLS tuple takes three parameters: url regex, function to check
# whether user has certain permission, and a function to redirect the user to a certain
# page if he doesn't have sufficient rights.
import re
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.http import HttpResponseRedirect
from django.contrib import messages
from backend.models import Professional
from django.contrib.auth.decorators import permission_required
def calculate_forbidden_response(request, view_func,view_args,view_kwargs):
if not request.user.is_authenticated():
return permission_required('')(view_func)(request,*view_args,**view_kwargs)
elif request.user.has_perm('backend.p_add_professional'):
messages.error(request, _('You need permission Spam to enter this cabinet.'))
return HttpResponseRedirect('/some_help_page_about_permissions.html')
def check_professional_permission(request):
return request.user.has_perm('backend.p_access_professional_cabinet')
RESTRICTED_URLS = (
(r'/professional/(.*)$', check_professional_permission, calculate_forbidden_response),
)
RESTRICTED_URLS_EXCEPTIONS = ()
class CheckPermissionMiddleware(object):
def __init__(self):
self.restricted = tuple([(re.compile(url[0]), url[1], url[2]) for url in RESTRICTED_URLS])
self.exceptions = tuple([re.compile(url) for url in RESTRICTED_URLS_EXCEPTIONS])
def process_view(self,request,view_func,view_args,view_kwargs):
if request.user.is_superuser:
return None
for path in self.exceptions:
if path.match(request.path): return None
for rule in self.restricted:
url, permission = rule[0], rule[1]
calculated_response = rule[2]
if url.match(request.path):
if not permission(request):
return calculated_response(request, view_func,view_args,view_kwargs)
else:
return None
return None