Seems like this isn't a unique problem, but I'm missing something in the solution. I'm using python-social-auth
and logging in with Google. All seems to be going well, until it gets to the create_user
part of the pipeline. I do have a custom User model and UserManager. On my User model I do have a role
property that is hooked up to some choices
. When social auth kicks in and logs someone in, it does call create_user
in my User manager, however it's only passing email, and no additional fields. I was attempting to hook into the pipeline and add the required role
property by adding it to the details
social auth dict, but that doesn't seem to have any effect. How should I go about hooking into the create user property to add fields that won't exist as far as social auth is concerned?
User Model
class User(AbstractBaseUser, PermissionsMixin):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
date_joined = models.DateTimeField(default=timezone.now)
email = models.EmailField(_("email address"), unique=True)
first_name = models.CharField(max_length=240, blank=True)
last_name = models.CharField(max_length=240, blank=True)
role = models.IntegerField(choices=RoleChoices.choices)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
objects = UserManager()
def __str__(self):
return self.email
@property
def full_name(self):
return f"{self.first_name} {self.last_name}".strip()
And my UserManager:
class UserManager(BaseUserManager):
"""
Custom user model manager where email is the unique identifiers
for authentication instead of usernames.
"""
def create_user(self, email, password, **extra_fields):
"""
Create and save a User with the given email and password.
"""
if not email:
raise ValueError(_("The Email must be set"))
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
if password is not None:
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password=None, **extra_fields):
"""
Create and save a SuperUser with the given email and password.
"""
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)
extra_fields.setdefault("is_active", True)
extra_fields.setdefault("role", 1)
if extra_fields.get("is_staff") is not True:
raise ValueError(_("Superuser must have is_staff=True."))
if extra_fields.get("is_superuser") is not True:
raise ValueError(_("Superuser must have is_superuser=True."))
return self.create_user(email, password, **extra_fields)
The Social auth config:
# Social Auth Config
AUTHENTICATION_BACKENDS = (
'social_core.backends.google.GoogleOAuth2',
'django.contrib.auth.backends.ModelBackend',
)
LOGIN_URL = 'login'
LOGOUT_URL = 'logout'
LOGIN_REDIRECT_URL = 'admin'
SOCIAL_AUTH_POSTGRES_JSONFIELD = True
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = os.getenv('GOOGLE_CLIENT_ID')
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = os.getenv('GOOGLE_CLIENT_SECRET')
SOCIAL_AUTH_USER_MODEL = 'search.User'
SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL = True
SOCIAL_AUTH_GOOGLE_OAUTH2_IGNORE_DEFAULT_SCOPE = True
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [
'https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/calendar.readonly',
'https://www.googleapis.com/auth/userinfo.profile',
'profile',
'email'
]
SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.auth_allowed',
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.user.get_username',
'search.socialauth.add_role',
'social_core.pipeline.user.create_user',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
)
And finally the add_role
function:
from .choices import RoleChoices
def add_role(**kwargs):
kwargs['details']['role'] = RoleChoices.ARTIST
return kwargs
The reason this isn't working is that the create_user
function explicitly filters the contents of details
to include only keys specified in a USER_FIELDS
setting. This defaults to
USER_FIELDS = ['username', 'email']
so anything else with just be ignored. It doesn't appear to be documented, but you should be able to override this by creating a setting as follows:
SOCIAL_AUTH_USER_FIELDS = ['username', 'email', 'role']
Which will then ensure your role
is passed through to the user instance.
The rest of your pipeline and configuration looks fine.