django social auth limiting user data - django

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.

Related

Django rest framework: How to reuse an app using different settings?

I have an application that has it's own urls and uses specific settings to access another api.
I would like to use this same app again within the same project, but with different urls and using a seperate endpoint.
So just setup new urls, and point to the same views from the original app but inject different settings.
For example one of my views is:
class SummaryVMsList(ListAPIView):
'''
VM Summary
'''
def list(self, request, *args, **kwargs):
'''
Return a list of processed vm's
'''
v_token = settings.VTOKEN
base_url = settings.VURL
v_password = settings.VPASSWORD
v_username = settings.VUSERNAME
session = Session()
session.headers.update({
'v_token': v_token
})
client = VClient(
url=base_url,
v_username=v_username,
v_password=v_password,
session=session
)
try:
repos = client.get_summary_vms()
return Response(data=repos, status=status.HTTP_200_OK)
except VError as err:
return Response(
data={'error': str(err)},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
# log the error
finally:
client.logout()
How would I be able to change the setting values: settings.VTOKEN, settings.VURL, settings.VPASSWORD and settings.VUSERNAME
Based on whick url is used:
In urls-site1.py
app_name = 'v_site1'
urlpatterns = [
path('vm-summary', views.SummaryVMsList.as_view(), name='vms_list'),
]
In urls-site2.py:
app_name = 'v_site2'
urlpatterns = [
path('vm-summary', views.SummaryVMsList.as_view(), name='vms_list'),
]
In this case, a better idea would be to store such params in constants instead of settings. The settings in Django used for different environments, in the end, it can be hard to work with it.
Also, take note that there are two exactly the same URL vm-summary and it could cause a name collision.

Zoho CRM Python SDK v2 initialization problem for Django

Im trying to integrate the Zoho CRM v2 SDK with my Django app.
On the Django runserver, im able to get access tokens and using the refresh method and store them in the zcrm_oauthtokens.pkl file. The sdk then automatically refreshes the access token using the refresh token, so no problem here. However on my production server (heroku) im getting this error message:
2019-01-16T11:07:22.314759+00:00 app[web.1]: 2019-01-16 11:07:22,314 - Client_Library_OAUTH - ERROR - Exception occured while fetching oauthtoken from db; Exception Message::'NoneType' object has no attribute 'accessToken'
It seems to me that the tokens are being saved to file, but when the sdk try to access them it is looking for them in a DB and not the file specified in the token_persistence_path.
In my settings.py I have this:
ZOHO_CLIENT_ID = config('ZOHO_CLIENT_ID')
ZOHO_CLIENT_SECRET = config('ZOHO_CLIENT_SECRET')
ZOHO_REDIRECT_URI = config('ZOHO_REDIRECT_URI')
ZOHO_CURRENT_USER_EMAIL = 'jamesalexander#mylastwill.co.uk'
ZOHO_PATH = os.path.join(BASE_DIR, 'wills_online', 'zoho')
zoho_config = {'apiBaseUrl': "https://www.zohoapis.com",
'currentUserEmail': ZOHO_CURRENT_USER_EMAIL,
'client_id': ZOHO_CLIENT_ID,
'client_secret': ZOHO_CLIENT_SECRET,
'redirect_uri': ZOHO_REDIRECT_URI,
'token_persistence_path': ZOHO_PATH}
and in a views file I have this:
from zcrmsdk import *
import logging
from django.shortcuts import HttpResponse
from wills.models import PersonalDetails, ZoHoRecord, WillDocument
from wills_online.decorators import start_new_thread
from wills_online.settings import zoho_config
logger = logging.getLogger(__name__)
class ZohoRunOnce:
def __init__(self):
self.already_run = False
def run_once(self):
if not self.already_run:
print('zoho init run once')
ZCRMRestClient.initialize(zoho_config)
self.already_run = True
zoho_init = ZohoRunOnce()
zoho_init.run_once()
print(zoho_config['token_persistence_path'])
def zoho_callback():
return HttpResponse(200)
#start_new_thread
def zoho_personal_details(request):
""" updates or create a user account on zoho on profile completion """
personal_details_ob = PersonalDetails.objects.get(user=request.user)
zoho_ob = ZoHoRecord.objects.get(user=request.user)
try:
if zoho_ob.account:
record = ZCRMRecord.get_instance('Accounts', zoho_ob.account)
record.set_field_value('Account_Name', request.user.email)
record.set_field_value('Name', personal_details_ob.full_name)
record.set_field_value('Email', request.user.email)
record.set_field_value('Address_Line_1', personal_details_ob.address_line_1)
record.set_field_value('Address_Line_2', personal_details_ob.address_line_2)
record.set_field_value('Post_Town', personal_details_ob.post_town)
record.set_field_value('Post_Code', personal_details_ob.post_code)
record.set_field_value('Dob_Day', personal_details_ob.dob_day)
record.set_field_value('Dob_Month', personal_details_ob.dob_month)
record.set_field_value('Dob_Year', personal_details_ob.dob_year)
record.set_field_value('Gender', personal_details_ob.sex)
record.set_field_value('Marital_Status', personal_details_ob.marital_status)
record.set_field_value('Partner_Name', personal_details_ob.partner_full_name)
record.set_field_value('Partner_Gender', personal_details_ob.partner_gender)
record.set_field_value('Partner_Email', personal_details_ob.partner_email)
record.set_field_value('Children', personal_details_ob.children)
record.set_field_value('Pets', personal_details_ob.pets)
record.update()
else:
user = ZCRMUser.get_instance(name='James Alexander')
record = ZCRMRecord.get_instance('Accounts')
record.set_field_value('Account_Owner', user)
record.set_field_value('Account_Name', request.user.email)
record.set_field_value('Name', personal_details_ob.full_name)
record.set_field_value('Email', request.user.email)
record.set_field_value('Address_Line_1', personal_details_ob.address_line_1)
record.set_field_value('Address_Line_2', personal_details_ob.address_line_2)
record.set_field_value('Post_Town', personal_details_ob.post_town)
record.set_field_value('Post_Code', personal_details_ob.post_code)
record.set_field_value('Dob_Day', personal_details_ob.dob_day)
record.set_field_value('Dob_Month', personal_details_ob.dob_month)
record.set_field_value('Dob_Year', personal_details_ob.dob_year)
record.set_field_value('Gender', personal_details_ob.sex)
record.set_field_value('Marital_Status', personal_details_ob.marital_status)
record.set_field_value('Partner_Name', personal_details_ob.partner_full_name)
record.set_field_value('Partner_Gender', personal_details_ob.partner_gender)
record.set_field_value('Partner_Email', personal_details_ob.partner_email)
record.set_field_value('Children', personal_details_ob.children)
record.set_field_value('Pets', personal_details_ob.pets)
response = record.create()
# save account id to db for future updates
zoho_ob.account = response.details['id']
zoho_ob.save()
except ZCRMException as ex:
logger.log(1, ex.status_code)
logger.log(1, ex.error_message)
logger.log(1, ex.error_details)
logger.log(1, ex.error_content)
print(ex.status_code)
print(ex.error_message)
print(ex.error_content)
print(ex.error_details)
Ive tried running ZCRMRestClient.initialize(zoho_config) in settings.py, with no luck.
My method for getting the access token and refresh token, which seems to work is:
import os
import pprint
from sys import argv
import django
import requests
import zcrmsdk
from django.conf import settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wills_online.settings')
django.setup()
def zoho_refresh_token(code):
""" supply a self client token from the zoho api credentials from web site """
zoho_config = {"apiBaseUrl": "https://www.zohoapis.com",
"currentUserEmail": settings.ZOHO_CURRENT_USER_EMAIL,
"client_id": settings.ZOHO_CLIENT_ID,
"client_secret": settings.ZOHO_CLIENT_SECRET,
"redirect_uri": settings.ZOHO_REDIRECT_URI,
"token_persistence_path": settings.ZOHO_PATH}
pprint.pprint(zoho_config)
print('working')
address = f'https://accounts.zoho.com/oauth/v2/token?code={code}&redirect_uri={settings.ZOHO_REDIRECT_URI}&client_id={settings.ZOHO_CLIENT_ID}&client_secret={settings.ZOHO_CLIENT_SECRET}&grant_type=authorization_code'
response = requests.post(address)
data = response.json()
pprint.pprint(data)
zcrmsdk.ZCRMRestClient.initialize(zoho_config)
oauth_client = zcrmsdk.ZohoOAuth.get_client_instance()
refresh_token = data['refresh_token']
print(type(refresh_token))
oauth_client.generate_access_token_from_refresh_token(refresh_token, settings.ZOHO_CURRENT_USER_EMAIL)
print(refresh_token)
print('finished')
if name == 'main':
zoho_refresh_token(argv[1])
This is driving me mad. Help would be greatly appreciated. This is my first post so go easy, lol.
For future reference, you will need to define persistence_handler_class and persistence_handler_path in your configuration dictionary. You will also need a handler class and a user-defined model to store the results. Sample code follows:
# settings.py
import zcrmsdk
configuration_dictionary = {
'apiBaseUrl': 'https://www.zohoapis.com',
'apiVersion': 'v2',
'currentUserEmail': ZOHO_CURRENT_USER_EMAIL,
'sandbox': 'False',
'applicationLogFilePath': '',
'client_id': ZOHO_CLIENT_ID,
'client_secret': ZOHO_CLIENT_SECRET,
'redirect_uri': ZOHO_REDIRECT_URI,
'accounts_url': 'https://accounts.zoho.com',
'access_type': 'online',
'persistence_handler_class': ZOHO_HANDLER_CLASS,
'persistence_handler_path': ZOHO_HANDLER_PATH,
}
zcrmsdk.ZCRMRestClient.initialize(configuration_dictionary)
# zoho.models.py
from django.db import models
from zcrmsdk.OAuthClient import ZohoOAuthTokens
class ZohoOAuthHandler:
#staticmethod
def get_oauthtokens(email_address):
oauth_model_instance = ZohoOAuth.objects.get(user_email=email_address)
return ZohoOAuthTokens(oauth_model_instance.refresh_token,
oauth_model_instance.access_token,
oauth_model_instance.expiry_time,
user_email=oauth_model_instance.user_email)
#staticmethod
def save_oauthtokens(oauth_token):
defaults = {
'refresh_token': oauth_token.refreshToken,
'access_token': oauth_token.accessToken,
'expiry_time': oauth_token.expiryTime,
}
ZohoOAuth.objects.update_or_create(user_email=oauth_token.userEmail, defaults=defaults)
class ZohoOAuth(models.Model):
refresh_token = models.CharField(max_length=250)
access_token = models.CharField(max_length=250)
expiry_time = models.BigIntegerField()
user_email = models.EmailField()
In this example ZOHO_HANDLER_CLASS = 'ZohoOAuthHandler' and ZOHO_HANDLER_PATH = 'zoho.models'
The first time you go to use this you will need a grant_token from https://accounts.zoho.com/developerconsole. For the scope use aaaserver.profile.READ,ZohoCRM.modules.ALL to start (see https://www.zoho.com/crm/developer/docs/api/oauth-overview.html#scopes)
Before you can use the api you'll need to run the code below in a django shell. This uses a grant token to generate your initial access and refresh tokens. Afterwards, the api should handle refreshing your access token.
grant_token = GRANT_TOKEN
import zcrmsdk
oauth_client = zcrmsdk.ZohoOAuth.get_client_instance()
oauth_tokens = oauth_client.generate_access_token(grant_token)

set time to live for each session separately KVsession flask

KBsession stores the session TTL based on PERMANENT_SESSION_LIFETIME is there a way to override this for specific sessions
EDIT:
so I have two different API for login I need to give any user login from one of them an infinite session TTL, the other one will take PERMANENT_SESSION_LIFETIME value
note: KBsession back-end is redis
I think the best way is use Session Interface to create specific processing. This is just an example, but I hope you can understand approach.
from flask import Flask, session as flask_session, jsonify
flask_app = Flask(__name__)
# just a few user types
UNIQUE_USER_TYPE = 'unique'
DEFAULT_USER_TYPE = 'default'
#flask_app.route('/login-default')
def login_default():
flask_session['user_type'] = DEFAULT_USER_TYPE
return 'login default done'
#flask_app.route('/login-unique')
def login_unique():
flask_session['user_type'] = UNIQUE_USER_TYPE
return 'login unique done'
#flask_app.route('/session-state')
def get_session_state():
return jsonify(dict(flask_session))
class UserTypeSessionInterface(SecureCookieSessionInterface):
def get_expiration_time(self, app, session):
"""
I just override method. Just demonstration.
It's called from save_session() and open_session()
"""
if session.get('user_type') == UNIQUE_USER_TYPE:
# set 1 hour for unique users
delta = datetime.utcnow() + timedelta(hours=1)
else:
# set 3 hour for default users
delta = datetime.utcnow() + timedelta(hours=3)
# add datetime data into session
session['lifetime'] = delta.strftime('%Y-%m-%dT%H:%M:%S')
return delta
# use our custom session implementation
flask_app.session_interface = UserTypeSessionInterface()
Now run server, open new private window, /login-default and /session-state:
# default behaviour
{
"lifetime": "2018-11-06T16:22:21",
"user_type": "default"
}
Open one more private window, /login-unique and /session-state:
# unique behaviour
{
"lifetime": "2018-11-06T14:25:17",
"user_type": "unique"
}
So, session store tool doesn't matter(redis, cassandra or something else). All what you need is just implement open_session() and save_session():
class YourSessionProcessor(SessionInterface):
def open_session(self, app, request):
# just do here all what you need
pass
def save_session(self, app, session, response):
# just do here all what you need
pass
flask_app.session_interface = YourSessionProcessor()
Also you can use custom session class(just an example):
from flask.sessions import SessionMixin
from werkzeug.datastructures import CallbackDict
class CustomSession(CallbackDict, SessionMixin):
def __init__(self, initial=None, sid=None):
def on_update(self):
self.modified = True
CallbackDict.__init__(self, initial, on_update=on_update)
self.sid = sid
self.modified = False
# YourSessionProcessor
def open_session(self, app, request):
# you can find any useful data in request
# you can find all settings in app.config
sid = request.cookies.get(app.session_cookie_name)
# ... do here everything what you need
return CustomSession(sid=sid)
Hope this helps.

AnonymousUser with django.test.client.login()

I'm testing login function.
def setUpClass(cls):
super(BasePage_loggedin, cls).setUpClass()
cls.selenium = WebDriver()
cls.client = Client()
cls.user_1 = MyUser.objects.create_user(username='myself',password='12345')
cls.client.login(username=cls.user_1.username, password=cls.user_1.password)
# create session cookie:
session = SessionStore()
session[SESSION_KEY] = cls.user_1.pk
session[BACKEND_SESSION_KEY] = settings.AUTHENTICATION_BACKENDS[0]
session[HASH_SESSION_KEY] = cls.user_1.get_session_auth_hash()
session.save()
# Finally, create the cookie dictionary
cookie = {
'name': settings.SESSION_COOKIE_NAME,
'value': session.session_key,
'secure': False,
'path': '/',
}
# add the session cookie
cls.selenium.get('{}'.format(cls.live_server_url))
cls.selenium.add_cookie(cookie)
cls.selenium.refresh()
cls.selenium.get('{}'.format(cls.live_server_url))
So I can pass the login page, but then, when I do request.user to check the data for this user, it's an AnonymousUser
When you're creating the user that way - I believe it has to do with the password. Setting the password to a string like that doesn't do what you think it would do.
You could create the user like that - then add this after the user creation but before the login:
cls.user_1.set_password('12345')
cls.user_1.save()
Then login the user with something like this:
cls.client.login(username=cls.user_1.username, password='12345')
I believe it has something to do with the hashing of the password or something along those lines - it's been a while since I stumbled around with it, but I remember having the exact same issue as you.
Something like this should work:
cls.selenium = WebDriver()
cls.client = Client()
cls.user_1 = MyUser.objects.create_user(username='myself',password='12345')
cls.user_1.set_password('12345')
cls.user_1.save()
cls.client.login(username=cls.user_1.username, password='12345')
Please check your settings.py and try below codes.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication', # needed only up to the test env
'rest_framework.authentication.TokenAuthentication',
)
}

flask-admin incorrect relative url

I am trying to create a simple admin for editing a mongo collection. I have posted the code below. It all works perfectly locally or as a docker container. However when I deploy this in our micro-service architecture the app lives at: SERVER_NAME/TEAM_NAME/APP_NAME/.
Flask routes set with #app.route work correctly. However the urls in the admin templates are not correct and always start directly at SERVER_NAME ignoring team-name and app-name. The actual pages and resources are located at the correct urls but the urls for the static resources are not found. How do I make sure the urls generated within flask-admin also take into account the relative url?
The code:
import os
import flask_admin
from wtforms import form, fields
from flask_admin.contrib.pymongo import ModelView, filters
# User admin
class WordPairsForm(form.Form):
text = fields.StringField("Text")
language = fields.SelectField("Language", choices=[("de", "german"), ("en", "english"), ("pl", "polish")])
label = fields.SelectField("Label", choices=[("badword", "bad word"), ("no_stay", "no overnight stay")])
active = fields.BooleanField("Active", default="checked")
class WordPairsView(ModelView):
column_list = ("text", "language", "label", "active")
column_sortable_list = ("text", "language", "label", "active")
column_searchable_list = ("text",)
column_filters = (
filters.FilterLike("text", "Text"),
filters.FilterNotLike("text", "Text"),
filters.FilterEqual("language", "Language", options=[("de", "german"), ("en", "english"), ("pl", "polish")]),
filters.FilterEqual("label", "Label", options=[("badword", "bad word"), ("no_stay", "no overnight stay")]),
filters.BooleanEqualFilter("active", "Active")
)
form = WordPairsForm
def create_form(self):
_form = super(WordPairsView, self).create_form()
return _form
def edit_form(self, obj):
_form = super(WordPairsView, self).edit_form(obj)
return _form
def get_list(self, *args, **kwargs):
count, data = super(WordPairsView, self).get_list(*args, **kwargs)
return count, data
def get_url
def add_admin(app):
admin = flask_admin.Admin(
app,
name="CQAS Admin",
url=os.getenv(
"F_ADMIN_URL",
"/admin"
),
static_url_path=os.getenv("F_ADMIN_STATIC_URL", None),
subdomain=os.getenv("F_ADMIN_SUBDOMAIN", None),
endpoint=os.getenv("F_ADMIN_ENDPOINT", None)
)
admin.add_view(WordPairsView(app.data.data, "WordPairs"))