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.
Related
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
I have implemented login form for username/password method, and that works perfect.
I want user to be also able to login using their social accounts.
I am using django-allauth to map social users to django-users.
Now I want to allow only those social accounts to login, that are mapped to django-users and not everyone.
Is there a way to override callback view? or something else can be done?
To simply disable registration, you have to overwrite the default account adaptor. If you also want to support social login, you also need to overwrite the default soculaaccount adapter. Add the following code somewhere in one of your apps (e.g. adapter.py):
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.exceptions import ImmediateHttpResponse
class NoNewUsersAccountAdapter(DefaultAccountAdapter):
def is_open_for_signup(self, request):
return False
class SocialAccountWhitelist(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
u = sociallogin.user
print('User {0} is trying to login'.format(u.email))
# Write code here to check your whitelist
if not_in_your_list(u):
raise ImmediateHttpResponse(HttpResponseRedirect('/account/login'))
and then add the following to your settings:
ACCOUNT_ADAPTER = 'path_to_app.adapter.NoNewUsersAccountAdapter'
SOCIALACCOUNT_ADAPTER = 'path_to_app.adapters.SocialAccountWhitelist'
After that, all you need to do is manually create an Account from the Admin pages, and manually create an EmailAddress. For the social login, you will need to write code to somehow check if the email is allowed
I would recommend you add a Staff-Only form to make this easy on you, where you can ask for username, email (and even password) and then do
new_user = Account.objects.create_user(email=email, username=username, password=password)
EmailAddress.objects.create(email=email, user=new_user, verified=True, primary=True)
You can also develop an Invitation scheme, but that is a lot more complicated but quickly googled and found the following project, which I have not personally used, but looks like what you need:
https://github.com/bee-keeper/django-invitations
Finally After reading the documents thoroughly and doing a lot of trials and errors I got to what I was looking for.
I had to set following parameters as a part of configuration specified in docs.
ACCOUNT_EMAIL_REQUIRED (=False)
The user is required to hand over an e-mail address when signing up.
and
SOCIALACCOUNT_QUERY_EMAIL (=ACCOUNT_EMAIL_REQUIRED)
Request e-mail address from 3rd party account provider? E.g. using OpenID AX, or the Facebook “email” permission.
I had to set ACCOUNT_EMAIL_REQUIRED = True as it was required to check if that email id is already registerd with us.
and then finally I overridden pre_social_login like below.
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
class NoNewSocialLogin(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
try:
cr_user = auth_user.objects.get(email=sociallogin.user.email)
if cr_user and cr_user.is_active:
user_login = login(request, cr_user, 'django.contrib.auth.backends.ModelBackend')
raise ImmediateHttpResponse(HttpResponseRedirect(reverse('protect')))
else:
raise ImmediateHttpResponse(render_to_response("account/authentication_error.html"))
except ObjectDoesNotExist as e:
raise ImmediateHttpResponse(render_to_response("socialaccount/authentication_error.html"))
except Exception as e:
raise ImmediateHttpResponse(HttpResponseRedirect(reverse('protect')))
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
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
I'm currently developing an application in Django and trying to implement Facebook authentication and requests to the Graph API. I've seen a few different libraries out there, but what is the best way to do the following:
Have a user login via Facebook.
Django creates a new user for them and adds their uid and oauth token.
I can then make calls to the Graph API using Facebook's Python SDK.
I did see this example. Is it that simple on normal Django?
My company has built a library that makes integrating Facebook into your Django application dead simple (we've probably built 10-20 apps with the library, including some with huge amounts of traffic, so it's been battle-tested).
pip install ecl-facebook==1.2.7
In your settings, add values for your FACEBOOK_KEY, FACEBOOK_SECRET, FACEBOOK_SCOPE, FACEBOOK_REDIRECT_URL, and PRIMARY_USER_MODEL. You'll also need to add ecl_facebook.backends.FacebookAuthBackend to your AUTHENTICATION_BACKENDS. For example, in settings.py:
# These aren't actual keys, you'll have to replace them with your own :)
FACEBOOK_KEY = "256064624431781"
FACEBOOK_SECRET = "4925935cb93e3446eff851ddaf5fad07"
FACEBOOK_REDIRECT_URL = "http://example.com/oauth/complete"
FACEBOOK_SCOPE = "email"
# The user model where the Facebook credentials will be stored
PRIMARY_USER_MODEL = "app.User"
AUTHENTICATION_BACKENDS = (
# ...
'ecl_facebook.backends.FacebookAuthBackend',
)
Add some views in your views.py to handle pre- and post-authentication logic.
from django.contrib.auth import authenticate, login
from django.http import HttpResponseRedirect
from ecl_facebook.django_decorators import facebook_begin, facebook_callback
from ecl_facebook import Facebook
from .models import User
# ...
#facebook_begin
def oauth_facebook_begin(request):
# Anything you want to do before sending the user off to Facebook
# for authorization can be done here.
pass
#facebook_callback
def oauth_facebook_complete(request, access_token, error):
if error is None:
facebook = Facebook(token)
fbuser = facebook.me()
user, _ = User.objects.get_or_create(facebook_id=fbuser.id, defaults={
'access_token': access_token})
user = authenticate(id=user.id)
login(request, user)
return HttpResponseRedirect("/")
else:
# Error is of type ecl_facebook.facebook.FacebookError. We pass
# the error back to the callback so that you can handle it
# however you want.
pass
Now just hook up these URLs in your urls.py file and you're done.
# ...
urlpatterns = patterns('app.views',
# ...
url(r'^oauth/facebook/begin$', 'oauth_facebook_begin'),
url(r'^oauth/facebook/complete$', 'oauth_facebook_complete'),
)
Hope this helps!
P.S. You can read the rest of the docs here.
We do a lot of Facebook Application development where I work, and so we've developed an open-source library that makes everything about it really easy.
from django.http import HttpResponse
from fandjango.decorators import facebook_authorization_required
#facebook_authorization_required
def foo(request, *args, **kwargs):
return HttpResponse("Your name is %s" % request.facebook_user.first_name)
I recommend https://github.com/egnity/fb.py. Got my Django-based Facebook app up and running in no time. It includes a middleware that allows you to run code like this in your view:
for the user id:
user_id = request.facebook.graph().get_object("me")['id']
for the oauth token:
user_token = request.facebook.auth_token
You can then add the above to your User model as you please. To make Graph API calls, you can still use fb.py's middleware -- no need for using the primitive python-sdk. The user_id code above is a perfect example of a Graph API call. There's much more you can do with fb.py. The download includes a sample django project to get you going.