How to populate user profile with django-allauth provider information? - django

I'm using django-allauth for my authentication system. I need that when the user sign in, the profile module get populated with the provider info (in my case facebook).
I'm trying to use the pre_social_login signal, but I just don't know how to retrieve the data from the provider auth
from django.dispatch import receiver
from allauth.socialaccount.signals import pre_social_login
#receiver(pre_social_login)
def populate_profile(sender, **kwargs):
u = UserProfile( >>FACEBOOK_DATA<< )
u.save()
Thanks!!!

The pre_social_login signal is sent after a user successfully
authenticates via a social provider, but before the login is actually
processed. This signal is emitted for social logins, signups and when
connecting additional social accounts to an account.
So it is sent before the signup is fully completed -- therefore this not the proper signal to use.
Instead, I recommend you use allauth.account.signals.user_signed_up, which is emitted for all users, local and social ones.
From within that handler you can inspect whatever SocialAccount is attached to the user. For example, if you want to inspect Google+ specific data, do this:
user.socialaccount_set.filter(provider='google')[0].extra_data
UPDATE: the latest development version makes this a little bit more convenient by passing along a sociallogin parameter that directly contains all related info (social account, token, ...)

Here is a Concrete example of #pennersr solution :
Assumming your profile model has these 3 fields: first_name, email, picture_url
views.py:
#receiver(user_signed_up)
def populate_profile(sociallogin, user, **kwargs):
if sociallogin.account.provider == 'facebook':
user_data = user.socialaccount_set.filter(provider='facebook')[0].extra_data
picture_url = "http://graph.facebook.com/" + sociallogin.account.uid + "/picture?type=large"
email = user_data['email']
first_name = user_data['first_name']
if sociallogin.account.provider == 'linkedin':
user_data = user.socialaccount_set.filter(provider='linkedin')[0].extra_data
picture_url = user_data['picture-urls']['picture-url']
email = user_data['email-address']
first_name = user_data['first-name']
if sociallogin.account.provider == 'twitter':
user_data = user.socialaccount_set.filter(provider='twitter')[0].extra_data
picture_url = user_data['profile_image_url']
picture_url = picture_url.rsplit("_", 1)[0] + "." + picture_url.rsplit(".", 1)[1]
email = user_data['email']
first_name = user_data['name'].split()[0]
user.profile.avatar_url = picture_url
user.profile.email_address = email
user.profile.first_name = first_name
user.profile.save()
If you are confused about those picture_url variable in each provider. Then take a look at the docs:
facebook:
picture_url = "http://graph.facebook.com/" + sociallogin.account.uid + "/picture?type=large" Docs
linkedin:
picture_url = user_data['picture-urls']['picture-url'] Docs
twitter:
picture_url = picture_url.rsplit("_", 1)[0] + "." + picture_url.rsplit(".", 1)[1] Docs And for the rsplit() take a look here
Hope that helps. :)

I am doing in this way and taking picture (field) url and google provider(field) as an example.
socialaccount_obj = SocialAccount.objects.filter(provider='google', user_id=self.user.id)
picture = "not available"
if len(socialaccount_obj):
picture = socialaccount_obj[0].extra_data['picture']
make sure to import : from allauth.socialaccount.models import SocialAccount

There is an easier way to do this.
Just add the following to your settings.py. For example, Linked in...
SOCIALACCOUNT_PROVIDERS = {
'linkedin': {
'SCOPE': [
'r_basicprofile',
'r_emailaddress'
],
'PROFILE_FIELDS': [
'id',
'first-name',
'last-name',
'email-address',
'picture-url',
'public-profile-url',
]
}
The fields are automatically pulled across.

Related

Flask dance example for login with Azure AD

I am trying to implement SSO for one of my applications using flask-login and flask-dance. As a starting point I am using sample code given on Flask Dance website - https://flask-dance.readthedocs.io/en/v1.2.0/quickstarts/sqla-multiuser.html
Only change I did was - I replaced GitHub with my Azure AD credentials
Please find the code below:
import sys
from flask import Flask, redirect, url_for, flash, render_template
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm.exc import NoResultFound
from flask_dance.contrib.github import make_github_blueprint, github
from flask_dance.contrib.azure import make_azure_blueprint, azure
from flask_dance.consumer.storage.sqla import OAuthConsumerMixin, SQLAlchemyStorage
from flask_dance.consumer import oauth_authorized, oauth_error
from flask_login import (
LoginManager, UserMixin, current_user,
login_required, login_user, logout_user
)
# setup Flask application
app = Flask(__name__)
app.secret_key = "XXXXXXXXXXXXXX"
blueprint = make_azure_blueprint(
client_id="XXXXXXXXXXXXXXXXXXXXX",
client_secret="XXXXXXXXXXXXXXXXXXXXXXXX",
tenant="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
)
app.register_blueprint(blueprint, url_prefix="/login")
# setup database models
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///multi.db"
db = SQLAlchemy()
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
# Your User model can include whatever columns you want: Flask-Dance doesn't care.
# Here are a few columns you might find useful, but feel free to modify them
# as your application needs!
username = db.Column(db.String(1028), unique=True)
email = db.Column(db.String(1028), unique=True)
name = db.Column(db.String(1028))
class OAuth(OAuthConsumerMixin, db.Model):
provider_user_id = db.Column(db.String(1028), unique=True)
user_id = db.Column(db.Integer, db.ForeignKey(User.id))
user = db.relationship(User)
# setup login manager
login_manager = LoginManager()
login_manager.login_view = 'azure.login'
#login_manager.user_loader
def load_user(user_id):
#print(User.query.get(int(user_id)))
return User.query.get(int(user_id))
# setup SQLAlchemy backend
blueprint.storage = SQLAlchemyStorage(OAuth, db.session, user=current_user,user_required=False)
# create/login local user on successful OAuth login
#oauth_authorized.connect_via(blueprint)
def azure_logged_in(blueprint, token):
if not token:
#print(token)
flash("Failed to log in with azure.", category="error")
return False
resp = blueprint.session.get("/user")
if not resp.ok:
#print(resp)
msg = "Failed to fetch user info from Azure."
flash(msg, category="error")
return False
azure_info = resp.json()
azure_user_id = str(azure_info["id"])
#print(azure_user_id)
# Find this OAuth token in the database, or create it
query = OAuth.query.filter_by(
provider=blueprint.name,
provider_user_id=azure_user_id,
)
try:
oauth = query.one()
except NoResultFound:
oauth = OAuth(
provider=blueprint.name,
provider_user_id=azure_user_id,
token=token,
)
if oauth.user:
login_user(oauth.user)
flash("Successfully signed in with Azure.")
else:
# Create a new local user account for this user
user = User(
# Remember that `email` can be None, if the user declines
# to publish their email address on GitHub!
email=azure_info["email"],
name=azure_info["name"],
)
# Associate the new local user account with the OAuth token
oauth.user = user
# Save and commit our database models
db.session.add_all([user, oauth])
db.session.commit()
# Log in the new local user account
login_user(user)
flash("Successfully signed in with Azure.")
# Disable Flask-Dance's default behavior for saving the OAuth token
return False
# notify on OAuth provider error
#oauth_error.connect_via(blueprint)
def azure_error(blueprint, error, error_description=None, error_uri=None):
msg = (
"OAuth error from {name}! "
"error={error} description={description} uri={uri}"
).format(
name=blueprint.name,
error=error,
description=error_description,
uri=error_uri,
)
flash(msg, category="error")
#app.route("/logout")
#login_required
def logout():
logout_user()
flash("You have logged out")
return redirect(url_for("index"))
#app.route("/")
def index():
return render_template("home.html")
# hook up extensions to app
db.init_app(app)
login_manager.init_app(app)
if __name__ == "__main__":
if "--setup" in sys.argv:
with app.app_context():
db.create_all()
db.session.commit()
print("Database tables created")
else:
app.run(debug=True,port=5011)
I have also done appropriate changes in HTML file for 'azure.login'.
So after running it as python multi.py --setup database tables are getting created
and after I run python multi.py Oauth dance is actually starting but in the end I am getting error like below:
HTTP Response:
127.0.0.1 - - [28/Oct/2020 10:17:44] "?[32mGET /login/azure/authorized?code=0.<Token>HTTP/1.1?[0m" 302 -
127.0.0.1 - - [28/Oct/2020 10:17:44] "?[37mGET / HTTP/1.1?[0m" 200 -
Am I missing something? Is it a good idea to use Flask Dance and Flask Login to have SSO with Azure AD? Or I should go with MSAL only along with Flask Session?
Kindly give your valuable inputs..
Since you use Azure AD as the Flask dance provider, we need to use Microsoft Graph to get user's information. The URL should be https://graph.microsoft.com/v1.0/me. So please update the code resp = blueprint.session.get("/user") to resp = blueprint.session.get("/v1.0/me") in method azure_logged_in. Besides, please note that the azure ad user's information has different names. We also need to update the code about creating users.
For example
#oauth_authorized.connect_via(blueprint)
def azure_logged_in(blueprint, token):
if not token:
# print(token)
flash("Failed to log in with azure.", category="error")
return False
resp = blueprint.session.get("/v1.0/me")
# azure.get
if not resp.ok:
# print(resp)
msg = "Failed to fetch user info from Azure."
flash(msg, category="error")
return False
azure_info = resp.json()
azure_user_id = str(azure_info["id"])
# print(azure_user_id)
# Find this OAuth token in the database, or create it
query = OAuth.query.filter_by(
provider=blueprint.name,
provider_user_id=azure_user_id,
)
try:
oauth = query.one()
except NoResultFound:
oauth = OAuth(
provider=blueprint.name,
provider_user_id=azure_user_id,
token=token,
)
if oauth.user:
login_user(oauth.user)
flash("Successfully signed in with Azure.")
else:
# Create a new local user account for this user
user = User(
# create user with user information from Microsoft Graph
email=azure_info["mail"],
username=azure_info["displayName"],
name=azure_info["userPrincipalName"]
)
# Associate the new local user account with the OAuth token
oauth.user = user
# Save and commit our database models
db.session.add_all([user, oauth])
db.session.commit()
# Log in the new local user account
login_user(user)
flash("Successfully signed in with Azure.")
# Disable Flask-Dance's default behavior for saving the OAuth token
return False
For more details, please refer to here and here

python-social-auth and impersonate django user

I want to avoid store personal information in database (no last names, no email). This is my approach to achieve it:
Delegate authentication to social networks authentication service ( thanks to python-social-auth )
Change python-social-auth pipeline to anonymize personal information.
Then I replaced social_details step on pipeline by this one:
#myapp/mypipeline.py
def social_details(strategy, response, *args, **kwargs):
import md5
details = strategy.backend.get_user_details(response)
email = details['email']
fakemail = unicode( md5.new(email).hexdigest() )
new_details = {
'username': fakemail[:5],
'email': fakemail + '#noreply.com',
'fullname': fakemail[:5],
'first_name': details['first_name'],
'last_name': '' }
return {'details': new_details }
settings.py
SOCIAL_AUTH_PIPELINE = (
'myapp.mypipeline.social_details',
'social.pipeline.social_auth.social_uid',
...
The question:
Is this the right way to get my purpose?
Looks good.
I'm doing something similar to anonymize IP addresses (hash them).

django social auth limiting user data

I have configured django social auth's to take from google only e-mail, but google shows this screen alerting app user that gender, date of birth, picture, language will be collect:
My django-social-auth config is as follow:
WHITE_LISTED_DOMAINS = [ 'some_domain', ]
GOOGLE_WHITE_LISTED_DOMAINS = WHITE_LISTED_DOMAINS
SOCIAL_AUTH_EXTRA_DATA = False
#LOGIN_ERROR_URL = '/login-error/' Not set
#SOCIAL_AUTH_DEFAULT_USERNAME = 'new_social_auth_user' Not set
#GOOGLE_CONSUMER_KEY = '' Not set
#GOOGLE_CONSUMER_SECRET = '' Not set
#GOOGLE_OAUTH2_CLIENT_ID = '' Not set
#GOOGLE_OAUTH2_CLIENT_SECRET = '' Not set
SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL = False
SOCIAL_AUTH_PROTECTED_USER_FIELDS = ['email',]
INSTALLED_APPS = (
'django.contrib.auth',
...
'social_auth',
)
How can I do to avoid this google message?
EDITED
I have move to GoogleOauth2 auth and inherit and change google backend:
from social_auth.backends.google import *
GOOGLE_OAUTH2_SCOPE = ['https://www.googleapis.com/auth/userinfo.email',]
class GoogleOAuth2(BaseOAuth2):
"""Google OAuth2 support"""
AUTH_BACKEND = GoogleOAuth2Backend
AUTHORIZATION_URL = 'https://accounts.google.com/o/oauth2/auth'
ACCESS_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token'
REVOKE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/revoke'
REVOKE_TOKEN_METHOD = 'GET'
SETTINGS_SECRET_NAME = 'GOOGLE_OAUTH2_CLIENT_SECRET'
SCOPE_VAR_NAME = 'GOOGLE_OAUTH_EXTRA_SCOPE'
DEFAULT_SCOPE = GOOGLE_OAUTH2_SCOPE
REDIRECT_STATE = False
print DEFAULT_SCOPE #<------ to be sure
def user_data(self, access_token, *args, **kwargs):
"""Return user data from Google API"""
return googleapis_profile(GOOGLEAPIS_PROFILE, access_token)
#classmethod
def revoke_token_params(cls, token, uid):
return {'token': token}
#classmethod
def revoke_token_headers(cls, token, uid):
return {'Content-type': 'application/json'}
But google still ask for profile data, profile is still in scope:
https://accounts.google.com/o/oauth2/auth?response_type=code&scope=https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/auth/userinfo.profile&redirect_uri=...
Runs fine if I modify by hand social-auth code instead inherit:
def get_scope(self):
return ['https://www.googleapis.com/auth/userinfo.email',]
What is wrong with my code?
That's because the default scope used on google backend is set to that (email and profile information), it's defined here. In order to avoid that you can create your own google backend which just sets the desired scope, then use that backend instead of the built in one. Example:
from social_auth.backends.google import GoogleOAuth2
class SimplerGoogleOAuth2(GoogleOAuth2):
DEFAULT_SCOPE = ['https://www.googleapis.com/auth/userinfo.email']
Those who don't know how to add in AUTHENTICATION_BACKENDS, if using the way Omab suggested you need to add newly defined backend in your setting.py file:
AUTHENTICATION_BACKENDS = (
'app_name.file_name.class_name', #ex: google_auth.views.SimplerGoogleOAuth2
# 'social_core.backends.google.GoogleOAuth2', # comment this as no longer used
'django.contrib.auth.backends.ModelBackend',
)
To know how to create the class SimplerGoogleOAuth2 check Omab's answer.

How to get all files of all users using google drive API

I am a 'domain admin' for a google account and would like to:
Get all of the users in my domain and "for each" user, give another user read-access everyone's files . I can get each user, but right now I do not understand how to get each users documents.
#!/usr/bin/python
import gdata.docs
import gdata.docs.service
from gdata.apps import client
userNameAtGmailCom = 'domainAdmin#someplace.com'
password = 'mypassword'
personToShareWith = "someGuy#gmail.com"
domain = 'someplace.com'
def login(userNameAtGmailCom, password, personToShareWith, domain):
client = gdata.apps.client.AppsClient(domain=domain)
client.ssl = True
client.ClientLogin(email=userNameAtGmailCom, password=password, source='apps')
all_users = client.RetrieveAllUsers()
for user in all_users.entry:
user_name = user.login.user_name
print user_name
password = user.login.password
print password
clientDocs = gdata.docs.service.DocsService()
#password always returns 'none'
#therefore I've commented out the 'bad authentication'
#that would happen if the lines below ran
#clientDocs.ClientLogin(user_name, password)
#documents_feed = clientDocs.GetDocumentListFeed()
#for document_entry in documents_feed.entry:
#print document_entry.title.text
#scope = gdata.docs.Scope(value=personToShareWith, type='user')
#role = gdata.docs.Role(value='reader')
#acl_entry = gdata.docs.DocumentListAclEntry(scope=scope, role=role)
#created_acl_entry = client.Post(acl_entry, document_entry.GetAclLink().href, converter=gdata.docs.DocumentListAclEntryFromString)
login(userNameAtGmailCom, password, personToShareWith, domain)
You should use the Google Drive API and use service accounts to perform Google Apps domain-wide delegation of authority:
https://developers.google.com/drive/delegation

How to get logged in user's uid from session in Django?

I have implemented a registration/login/authentication system using this Django guide.
But, how would I access a user's information from my views so I can send the user's information to a template file?
I want to be able to access a user's ID so I can submit a form with the user's ID attached to the form.
In case anyone wants to actually extract a user ID from an actual Session object (for whatever reason - I did!), here's how:
from django.contrib.sessions.models import Session
from django.contrib.auth.models import User
session_key = '8cae76c505f15432b48c8292a7dd0e54'
session = Session.objects.get(session_key=session_key)
session_data = session.get_decoded()
print session_data
uid = session_data.get('_auth_user_id')
user = User.objects.get(id=uid)
Credit should go to Scott Barnham
There is a django.contrib.auth.models.User object attached to the request; you can access it in a view via request.user. You must have the auth middleware installed, though.
This:
def view(request):
if request.user.is_authenticated:
user = request.user
print(user)
# do something with user
An even easier way to do this is to install django-extensions and run the management command print_user_for_session.
And this is how they do it:
https://github.com/django-extensions/django-extensions/blob/master/django_extensions/management/commands/print_user_for_session.py
In case hwjp solution doesn't work for you ("Data is corrupted"), here is another solution:
import base64
import hashlib
import hmac
import json
def session_utoken(msg, secret_key, class_name='SessionStore'):
key_salt = "django.contrib.sessions" + class_name
sha1 = hashlib.sha1((key_salt + secret_key).encode('utf-8')).digest()
utoken = hmac.new(sha1, msg=msg, digestmod=hashlib.sha1).hexdigest()
return utoken
def decode(session_data, secret_key, class_name='SessionStore'):
encoded_data = base64.b64decode(session_data)
utoken, pickled = encoded_data.split(b':', 1)
expected_utoken = session_utoken(pickled, secret_key, class_name)
if utoken.decode() != expected_utoken:
raise BaseException('Session data corrupted "%s" != "%s"',
utoken.decode(),
expected_utoken)
return json.loads(pickled.decode('utf-8'))
s = Session.objects.get(session_key=session_key)
decode(s.session_data, 'YOUR_SECRET_KEY'))
credit to: http://joelinoff.com/blog/?p=920