Handling duplicate email address in django allauth - django

What I am trying to do ?
I am trying to avoid duplicate email address by following these steps:
Before user login to my site from a social account I check to see if the email address already exists.
If no then login the user otherwise check the below steps.
Check to see if the provider of registered user match the user trying to login.
If no then don't allow user to login otherwise login the user.
What is the problem ?
I get the following error:
Error:AttributeError at /accounts/twitter/login/callback/
'QuerySet' object has no attribute 'profile'
My Code:
views.py:
#receiver(pre_social_login)
def handleDuplicateEmail(sender, request, sociallogin, **kwargs):
if sociallogin.account.provider == 'facebook' or sociallogin.account.provider == 'twitter':
email_address = sociallogin.account.extra_data['email'] # get email address from fb or twitter social account.
else:
email_address = sociallogin.account.extra_data['email-address'] # Get email from linkedin social account.
users = User.objects.all().filter(email=email_address) # This line is problematic
if users:
if not (users.profile.provider == sociallogin.account.provider): # Different user is trying to login with already existing user's email address.
response = 'Your social account email address is already registered to some account. Please choose a different one.'
raise ImmediateHttpResponse(render(request, 'index.html', {'type': True, 'response': response})) # redirect to index template with error message.
models.py:
class Profile(models.Model):
user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE)
provider = models.CharField(max_length=256, blank=True, null=True) # for storing name of provider used to login with.
Edit:
Since Facebook, Twitter and Linkedin give users a choice to login with their phone number or email address and if they choose phone number then in that cause users won't have an email address associated with them to handle this situation I have updated my code like so:
if sociallogin.account.provider == 'facebook' or sociallogin.account.provider == 'twitter':
try:
email_address = sociallogin.account.extra_data['email']
except:
email_address = None # If social account was created on phone number for facebook & twitter
else:
try:
email_address = sociallogin.account.extra_data['email-address']
except:
email_address = None # If social account was created on phone number or linkedin
if email_address:
users = User.objects.all().filter(email=email_address)
if users.exists():
...
else:
response = 'You have not provided an email address in your social account. Please register as local user or use a different social account.'
raise ImmediateHttpResponse(render(request, 'index.html', {'type': True, 'response': response}))

users = User.objects.all().filter(email=email_address) returns a QuerySet so you can't just call .profile on it. In theory this query could return multiple User objects. But it could also contain 0 objects (which is more likely).
So you need to handle these cases:
if users.exists():
user = users.first() # assuming there will always only be one user
if not user.profile.provider == sociallogin.account.provider:
etc...
or
if users.exists():
for user in users:
if not user.profile.provider == sociallogin.account.provider:
etc...
break

Related

django permissions to restrict view based on groups

I currently have an order management app. But the team I am creating this for has two departments the office and the factory who'll be using the same app but with each team having access to what they need. I'm confused if I should create two different apps to login into the same system (but that would redundant code) or if there's any other way I can set permissions. I have tried using the django admin permissions and they don't seem to work.
you can create different html pages for different teams like office team have their own html page and factory team have different html page .
def user_login(request):
if request.method == 'POST':
username=request.POST.get('username')
password = request.POST.get('password')
user = authenticate(username=username,password=password)
if user:
if user.is_active:
if user.is_admin:
login(request,user)
return HttpResponseRedirect(reverse('adminpage'))
#return render(request,'admin.html')
else:
login(request,user)
return HttpResponseRedirect(reverse('userpage'))
#return render(request,'user.html')
else:
return HttpResponseRedirect('Account not active')
else:
#message={"info":"someone tried to login and failed! ","details":"username :{} and password: {}".format(username,password)}
return HttpResponse("someone tried to login and failed ! <br />details: <br />username: {} <br /> password:{} ".format(username,password))
else:
return render(request,'login.html')
you can create models with specified role as active according to condition.(you can create radio button for factory and office and according to radio button you models code work.
class MyUser(AbstractBaseUser):
email = models.EmailField(verbose_name='email address',max_length=255,unique=True,)
full_name = models.CharField(max_length=255,null=True,blank=True)
date_of_birth = models.DateField()
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_agent = models.BooleanField(default=False)
choices are :
USER_CHOICES = (
('is_admin','ADMIN'),
('is_agent', 'AGENT')
)
You can have the same authentication page for both users and staff and redirect them to the same page, (suppose the dashboard ). Now you can add a link in the dashboard which is visible only if the logged in user is a superuser. The link would redirect to an admin interface.
The dashboard could be something common for both users.
Hope this helps.

Django allauth with email as username and multiple sites

Is it possible to use Django allauth with the authentication method set to 'email' when using it on multiple sites?
I'm aiming to allow a user with the email address bob#example.com to create an account at site1.com and a separate account at site2.com.
In order to use email authentication, I need to leave UNIQUE_EMAIL set to True in the settings but this prevents users who already have accounts in one site from creating accounts in the other site.
I am assuming you'd like to allow the same email to be registered separately for each of the sites in your Django setup.
Looking at the allauth code; it appears that it is infeasible to do so at the moment, likely because allauth does not take into account site ID as part of the User signup process.
class AppSettings(object):
class AuthenticationMethod:
USERNAME = 'username'
EMAIL = 'email'
USERNAME_EMAIL = 'username_email'
class EmailVerificationMethod:
# After signing up, keep the user account inactive until the email
# address is verified
MANDATORY = 'mandatory'
# Allow login with unverified e-mail (e-mail verification is
# still sent)
OPTIONAL = 'optional'
# Don't send e-mail verification mails during signup
NONE = 'none'
def __init__(self, prefix):
self.prefix = prefix
# If login is by email, email must be required
assert (not self.AUTHENTICATION_METHOD ==
self.AuthenticationMethod.EMAIL) or self.EMAIL_REQUIRED
# If login includes email, login must be unique
assert (self.AUTHENTICATION_METHOD ==
self.AuthenticationMethod.USERNAME) or self.UNIQUE_EMAIL
One way to do this would be as follows:
- Keep allauth AUTHENTICATION_METHOD as Username
- Store the site alongside the User information, perhaps in a UserProfile or by overriding the User Model.
- Make the combination of Email and Site unique.
- Override the LoginView such that the user enters email; you can translate the combination of Email, Site to a Unique User account and username; which you can pass on to allauth to perform login.
Assuming you use the Sites framework; your code would look something like this:
from allauth.account.views import LoginView
from django.core.exceptions import ObjectDoesNotExist
class CustomLoginView(LoginView):
def get_user():
email = request.POST.get('email')
current_site = Site.objects.get_current()
try:
user = User.objects.get(email=email, site=current_site)
except ObjectDoesNotExist:
pass # Handle Error: Perhaps redirect to signup
return user
def dispatch(self, request, *args, **kwargs):
user = self.get_user()
request.POST = request.POST.copy()
request.POST['username'] = user.username
return super(CustomLoginView, self).dispatch(request, *args, **kwargs)
Then monkey-patch the LoginView with the custom login view:
allauth.account.views.LoginView = CustomLoginView
Related Reading on setting up a Site FK, and custom auth backends:
How to get unique users across multiple Django sites powered by the "sites" framework?
https://docs.djangoproject.com/en/dev/topics/auth/#writing-an-authentication-backend

Django all_auth and custom form

I'm using django allauth for social login/signup. Also, I've my own signup form as an alternate login/signup. Following are the fields that I'm fetching from the user in the alternate form.
class Profile(models.Model):
col1 = models.CharField(max_length=50, blank=True, null=True)
col2 = models.CharField(max_length=50, blank=True, null=True)
user = models.OneToOneField(User)
So, when the user signs up, it asks for additional fields as well(col1, col2), apart from username, email and password.
Following is the signup view.
user = User.objects.create_user(username, user_email, user_pass)
Profile.objects.create(user=user, col1=col1, col2=col2)
return
So, whenever the user signs up via the alternate form, the above view is called up.
Now, in contrast, when the user signs up from social account FB, it does not ask for extra info, ie col1/col2. It directly signs up without asking for extra info, neither I want it to ask.
I then create a row in Profile model post signup using signals.
#receiver(user_signed_up)
def create_profile(request, user, sociallogin=None, **kwargs):
if sociallogin:
if sociallogin.account.provider == 'facebook':
data = sociallogin.account.extra_data
col1 = data.get('col1')
col2 = data.get('col2')
Profile.objects.create(user=user, col1=col1, col2=col2)
So, (1) my problem is when creating a user using alternate form, no record is inserted in allauth tables, which i find weird.
(2) Consider, I signed up using alternate form using E1 as email id. Now I signup via allauth(FB) with the same id, it throws an error.
(3) How do I send confirmation mail to the users who signed up in alternate form using all_auth.
I played around with the library a bit and finally found the solution to my question. I'm pasting it over here for other's to review.
Add a signal pre_social_login that'll check for conditions.
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin=None, **kwargs):
if sociallogin:
user = User.objects.filter(email=email).first()
# If user already exists in custom local form, then login directly.
# Save/Update his details in social login tables.
if user:
# create or update social login tables to maintain the uniformity in the code.
token = sociallogin.token.token
socialaccount = SocialAccount.objects.filter(user=user).first()
if socialaccount: # If it exists, then update social tables.
# updating account.
socialaccount.extra_data = extra_data
socialaccount.save()
# updating token.
SocialToken.objects.filter(account=socialaccount) \
.update(token=token)
else: # else create.
# saving Social EmailAddress.
EmailAddress.objects.create(email=email, user=user)
# saving social account.
provider = 'facebook'
kwargs = {
'uid': extra_data.get('id'),
'provider': provider,
'extra_data': extra_data,
'user': user
}
socialaccount = SocialAccount.objects.create(**kwargs)
# saving social token.
app = SocialApp.objects.get(provider=provider)
kwargs = {
'token': token,
'account': socialaccount,
'app': app
}
SocialToken.objects.create(**kwargs)
# finally login.
user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)
raise ImmediateHttpResponse(redirect(reverse('index')))

Allauth - How to disable reset password for social accounts?

I want social users are only able to login without password. Therefore, I want to disable password reset at /accounts/password/reset/ for social users.
I know that I need to add a conditional in this code at allauth.accounts.forms.py
class ResetPasswordForm(forms.Form):
email = forms.EmailField(
label=_("E-mail"),
required=True,
widget=forms.TextInput(attrs={"type": "email", "size": "30"}))
def clean_email(self):
email = self.cleaned_data["email"]
email = get_adapter().clean_email(email)
self.users = filter_users_by_email(email)
if not self.users.exists():
raise forms.ValidationError(_("The e-mail address is not assigned"
" to any user account"))
return self.cleaned_data["email"]
I'm thinking about this solution, but I don't know how to do it properly:
elif SocialAccount.objects.filter(provider='facebook'):
raise forms.ValidationError(_("You are using a social account.")
Allauth extendability is not a problem. self.users contain all users with provided email but if you have setting ACCOUNT_UNIQUE_EMAIL=True or just didn't change it (so it's True by default) then you can take just first user. Every user contain socialaccount_set related manager. So we just filter that set by provider name and if there is any item then this user has related socialaccount. So you can inherit allauth form and put additional check there:
# forms.py
class MyResetPasswordForm(ResetPasswordForm):
def clean_email(self):
email = self.cleaned_data["email"]
email = get_adapter().clean_email(email)
self.users = filter_users_by_email(email)
if not self.users.exists():
raise forms.ValidationError(_("The e-mail address is not assigned"
" to any user account"))
# custom code start
elif self.users.first().socialaccount_set.filter(provider='facebook').exists():
raise forms.ValidationError(_("You are using a social account.")
# custom code end
return self.cleaned_data["email"]
And then override allauth settings to use your form in their reset password view:
# your settings
ACCOUNT_FORMS = {
'reset_password': 'your_app.forms.MyResetPasswordForm'
}
I have not tested this code so it can contain typos so feel free to post any further questions.
#bellum answer is kind off right but now Allauth return a list on filter_users_by_email(email)
So this worked for me
from allauth.account.forms import SignupForm, LoginForm, ResetPasswordForm
from allauth.account.adapter import get_adapter
from django.utils.translation import ugettext_lazy as _
from allauth.account.utils import filter_users_by_email
class MyResetPasswordForm(ResetPasswordForm):
def clean_email(self):
email = self.cleaned_data["email"]
email = get_adapter().clean_email(email)
self.users = filter_users_by_email(email)
if not self.users:
raise forms.ValidationError(_("Unfortunately no account with that"
" email was found"))
# custom code start
elif self.users[0].socialaccount_set.filter(provider='google').exists():
raise forms.ValidationError(_("Looks like the email/password combination was not used. Maybe try Social?"))
# custom code end
return self.cleaned_data["email"]
Note: I am using google social authentication if you are using Facebook then change it to facebook.

Problem with custom Authentication Backend for Django

I'm having a problem with a custom Authentication Backend I've built for an Active Directory via LDAP authentication.
The problem is that from the admin login page, after it properly authenticates and creates the new user in the database (or updates their info from the LDAP server), but then returns me to the admin login page indicating that I failed to enter a valid username and password.
Considering it authenticates and creates/updates the user in the django database, what am I doing wrong?
The code:
import ldap
import re
from django.conf import ad_settings
grps = re.compile(r'CN=(\w+)').findall
def anyof(short_group_list, adu):
all_groups_of_user = set(g for gs in adu.get('memberOf',()) for g in grps(gs))
return any(g for g in short_group_list if g in all_groups_of_user)
class ActiveDirectoryBackend(ModelBackend):
"""
This backend utilizes an ActiveDirectory server via LDAP to authenticate
users, creating them in Django if they don't already exist.
"""
def authenticate(self, username=None, password=None):
con = None
ldap.set_option(ldap.OPT_REFERRALS, 0)
try:
con = ldap.initialize('ldap://%s:%s' % (ad_settings.AD_DNS_NAME,
ad_settings.AD_LDAP_PORT))
con.simple_bind_s(username+"#"+ad_settings.AD_DNS_NAME, password)
ADUser = con.search_ext_s(ad_settings.AD_SEARCH_DN,
ldap.SCOPE_SUBTREE,
"sAMAccountName=%s" % username,
ad_settings.AD_SEARCH_FIELDS)[0][1]
con.unbind()
except ldap.LDAPError:
return None
# Does user belong to appropriate AD group?
if not anyof(ad_settings.PROJECTCODE,ADUser):
return None
# Does user already exist in Django?
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
#create Django user
user = User(username=username, is_staff = True, is_superuser = False)
#Update User info from AD
if ADUser.has_key('givenName'):
user.first_name = ADUser.get('givenName')[0]
if ADUser.has_key('sn'):
user.last_name = ADUser.get('sn')[0]
if ADUser.has_key('mail'):
user.email = ADUser.get('mail')[0]
# Does not store password in Django.
user.set_unusable_password()
user.save()
return user
EDIT: Figured out. Users cannot log in unless they are active (even though the documentation does not say that). Therefore, in the code given, the line that creates the new user should look like:
user = User(username=username, is_staff = True, is_Active = True,
is_superuser = False)
Figured out. Users cannot log in unless they are active (even though the documentation does not say that). Therefore, in the code given, the line that creates the new user should look like:
user = User(username=username, is_staff = True, is_Active = True,
is_superuser = False)