Django Social Auth store username from Facebook in Custom User Model - django

I've just integrated Django-Social-Auth (DSA) v0.7.23 using django 1.5 with a custom User model and auth/login is working fine except for the username field which is not being stored in my custom User model.
Stepping through the DSA code it appears to explicitly remove the username which was passed back by facebook. Here is the function in question that pops the username off:
#classmethod
def username_field(cls, values):
user_model = cls.user_model()
if hasattr(user_model, 'USERNAME_FIELD'):
# Django 1.5 custom user model, 'username' is just for internal
# use, doesn't imply that the model should have an username field
values[user_model.USERNAME_FIELD] = values.pop('username') # Username removed
return values
How can I go about getting the username that was passed back from facebook and not having it be explicitly removed by DSA?
I believe a work around would be to create a custom pipeline that generates a username. However I was wondering if anyone else encountered this scenario before and leveraged anything that already exists within DSA (i.e. a particular settings.py configuration)
Thanks.

The original username is available in the details attribute passed to pipeline functions, take this for example:
def generated_username(user, details, *args, **kwargs):
username = details['username']
user.your_field = username
user.save()
It's worth noting that the username is popped from values if you have a USERNAME_FIELD defined in your custom model.

Related

The relation between a model and it's form in Django, and user authentication

I'm migrating something from an old PHP/apache server to Django. I'm a bit stumped with the 'ModelForm'.
As far as I understand, a "Model" is the abstraction for persistent elements in my website/server - specifically this is something stored physically, say in a database, and defines the fields (read columns) in the DB.
I started moving the authentication part of the site, and discovered models, and specifically the User model (I made an empty User inheriting AbstractUser just in case I will ever need to extend things). Now I want to create a simple two field form, to authenticate login.
The form:
Username (which is a field of User, by default)
Password (Which is not).
Even the 'Username' needs a redefinition in the model form. So my questions:
What is the advantage of the model form (over just a form)? - seems like you're redefining fields anyway, and obviously sometimes adding fields on top of the model.
Specifically for authentication, I probably need to store my salted hash associated with the user somehow, compare my password using that and retrieve the user object. This is something I find very hard to find in the Django docs - they just have too much written on authentication, and not one full code example. Do I put this in the "validate" method of form, retrieving there an object and storing it in a session or something?
If there is a deeper relation between a model form and the associated model, I would like to know as well.
Simple django forms and modelforms have quite differences.
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ['pub_date', 'headline', 'content', 'reporter']
The above example illustrates that you don't have to write any form field in here. The model form will itself create a form which is based on the attributes provided in the model ('Article' in this example).
If you create a simple django form then it would be something like:
class ArticleForm(forms.Form):
some_field = forms.CharField(some_attrs)
...
The django User model provides you everything you need for authentication. When you want to create users just import django.contrib.auth.models.User and use create method to create objects. Then when you want to authenticate a user use authenticate method.
from django.contrib.auth import authenticate, login
def user_login(request):
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(request, username=username, password=password)
# after authentication login the user or set cookies or modify the session or some other action can be taken
return HttpResponse("Some response or use render for html page")
username and password will be coming from your post request.
If you want to extend default Django user model you can use django user model as onetoonefield in your extended model.
class AppUser(models.Model):
user = models.OneToOneField(User)
... # other custom fields

User without username with allauth, rest_auth and facebook

I have a setup with django-rest-framework, rest-auth, allauth and facebook Oauth2. My problem here is that when I created an user using the facebook endpoint, the social user was created, the django user was also created but both have no username. Should I configure this somewhere?
The username field is no longer present, because it was deprecated with Graph API v2.0 one year ago...
See
https://developers.facebook.com/docs/apps/changelog#v2_0_graph_api
/me/username is no longer available.
Check if you're using the newest versions of your libraries.
you can set custom username by overriding DefaultSocialAccountAdapter
i just removed # from email and replaced by _ to generate username
#in setting added this
SOCIALACCOUNT_ADAPTER = 'trip2.users.adapter.SocialAdapter'
# and then override the Adapter
class SocialAdapter(DefaultSocialAccountAdapter):
def populate_user(self,
request,
sociallogin,
data):
"""
Hook that can be used to further populate the user instance.
For convenience, we populate several common fields.
Note that the user instance being populated represents a
suggested User instance that represents the social user that is
in the process of being logged in.
The User instance need not be completely valid and conflict
free. For example, verifying whether or not the username
already exists, is not a responsibility.
"""
username = data.get('username')
first_name = data.get('first_name')
last_name = data.get('last_name')
email = data.get('email')
name = data.get('name')
user = sociallogin.user
emailtouname = email.split('#')[0] + "_" + email.split('#')[1].split('.')[0]
user_email(user, valid_email_or_none(email) or '')
name_parts = (name or '').partition(' ')
user_field(user, 'first_name', first_name or name_parts[0])
user_field(user, 'last_name', last_name or name_parts[2])
user_username(user, username or emailtouname)
return user
You can pass a 'username' key along with other data retrieved via Facebook API. Or you can dig into allauth/socialaccount/adapter.py populate_user() method and customize the 'username' field (I simply make it equal to user email)

Longer username in Django 1.7

I want to increase the length of the username in django from 30 to around 80, I know it may be duplicate question but the previous answers are not working, for example https://kfalck.net/2010/12/30/longer-usernames-for-django
this is for Django 1.2.
Did anyone try similar hack for Django>1.5
Thanks in advance
In Django 1.5 and above, the recommended approach would be to create a custom user model. Then you can make the username field exactly as you want.
I had the same problem few days ago. Finally, I ended just with cutting off first 30 characters of the (old) username (into the new database table), and adding a custom authentication backend that will check the email instead of user name. Terrible hack I know, and I'm planning to fix it as soon as I have some time. The idea is following:
I already have a model class that has one-to-one relation with djangos auth.User. I will add another field there called full_username.
class MyCustomUserModel(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL, related_name="custom_user")
full_username = models.CharField(max_length=80, ...)
...
Then, I'll add another custom authentication backend that will check this field as username. It would look something like this:
from django.contrib.auth.backends import ModelBackend
class FullUsernameAuthBackend(ModelBackend):
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
user = UserModel._default_manager.filter(custom_user__full_username=username)
# If this doesn't work, will use (the second case):
# user = MyCustomUserModel.objects.filter(full_username=username).user
if user.check_password(password):
return user
except UserModel.DoesNotExist:
# Adding exception MyCustomUserModel.DoesNotExist in "(the second case)"
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (#20760).
UserModel().set_password(password)
After this, you need to change settings.py:
AUTHENTICATION_BACKENDS = (
"....FullUsernameAuthBackend",
# I will have the email auth backend here also.
)
I hope that it will work.
Custom User Models are a huge change to make and aren't always compatible with apps. I solved it by running this very pragmatic migration. Note this only solves it at the database level.
migrations.RunSQL("alter table auth_user alter column username type varchar(254);")

Django username field nullable breaks default auth backend

when using a custom User model, extending AbstractBaseUser, I've run into a problem when the USERNAME_FIELD points to a nullable field
what I'm trying to accomplish is to allow multiple methods of login (facebook, username) and for that I have two unique identifier fields (facebook_id, username) but both have to be nullable because an account doesn't have to have both (and most accounts won't have both)
so for logging in with username, I rely on Django's built in login auth backend
where it falls apart is when trying to log in with an alternative login method (facebook) and the Django auth is attempted first
class ModelBackend(object):
"""
Authenticates against settings.AUTH_USER_MODEL.
"""
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
user = UserModel._default_manager.get_by_natural_key(username)
if user.check_password(password):
return user
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (#20760).
UserModel().set_password(password)
the if username is None hack is meant to fix the situation where the username field isn't actually called "username" so the username parameter is empty but the actual provided username can be found in kwargs
the issue is that it ends up with None value anyway (because kwargs contains facebook_id=12345) and ends up passing that to get_by_natural_key
def get_by_natural_key(self, username):
return self.get(**{self.model.USERNAME_FIELD: username})
which translates roughly to User.objects.get(username=None)
since my username field is nullable, this returns multiple results, the whole thing throws an models.MultipleObjectsReturned exception ("get() returned more than one User -- it returned 417!") and prevents any other auth backends from running
from my perspective there's several issues:
ModelBackend shouldn't be just blindly attempting to retrieve a user if its attempts at figuring out the provided username result with None
get_by_natural_key should catch the MultipleObjectsReturned and gracefully fail, or authenticate() should handle that scenario and fail to log the user in, instead of bombing the entire auth mechanism
known workarounds:
I know I can move the ModelBackend to be the last method attempted but that just makes the dumb query run sometimes instead of always, I'd rather fix it so it never does. I'm also slightly concerned with its performance as number of username=None users grows
I also know I can extend ModelBackend, patch the holes and just not use the stock ModelBackend, but I don't know if there are any gotchas to that approach... are existing sessions using ModelBackend lost?
For simplification purposes, I skipped the part where I actually use a nullable email field as the username :D and I know that for that I could just easily use a custom auth backend but once I got nullable-email-field-login working I started thinking I can probably save myself some headache in generating usernames for users coming from facebook (and letting them choose an unused username later on in the process) by making username field nullable as well :D
I'm concerned that having a nullable USERNAME_FIELD is going to make Django fail in various different ways, and I saw no warning for this or explanation for why. It's silly that a system that supports multiple login schemes cannot survive it's stock auth identifier being empty.
Since you're replacing the default User model with your own, I'd go with patching its manager's get_by_natural_key method to raise DoesNotExist when called with a None natural key, as that situation clearly indicates your user is being authenticated using some other scheme. Your model has no situation where a record can have a legitimate None natural key, so in any case it should raise a DoesNotExist for such a key.

How to customize the default CREATE_USER functionality in django auth

I am using django-social-auth for facebook login which creates a new user if it doesn't exist in the db, else updates the existing db entry. I think it checks for fb_username OR fb_uid in the database to check if the user exists or not.
Requirements:
In my application, i need to be able to create a user by manually inserting his info in db. And this row should be updated when the user logs_in with facebook.
So I manually created an entry with all the information of the user i.e. fb_username, fb_uid, email, first_name, last_name, hometown... etc.
But what happens when the user logs_in is, a new entry is created instead of updating the manually created entry. So i really don't understand what fields does it use to check if the user exists or not. What would be the best way to go from here?
Extra Info: I am extending the user model of django to UserProfile using a one-to-one relationship and AUTH_PROFILE_MODULE = 'accounts.UserProfile'
The only way to do it was editing this default pipeline
'social_auth.backends.pipeline.user.create_user',
by changing the
def create_user(backend,details,response,username,user=None,*args,**kwargs):
if user:
return {'user': user}
if not username:
return None
to
def create_user(backend,details,response,username,user=None,*args,**kwargs):
username = User.objects.get(u_name=username)
if username:
return {'user': username}
if not username:
return None