Django social auth how to get permission or die trying - python-social-auth

In my app, I need an email for every user. Facebook inconveniently for me does not provide it as a part of public profile. So I ask for permission:
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email', 'public_profile']
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {
'fields': 'id,name,email',
}
Now, user has the option to not grant the email permission. I want to make registration impossible without granting this permission. How do I do it?

You need to add function to the authentication pipeline that enforces the email requirement or raises AuthForbidden instead. Here's an example:
from social_core.exceptions import AuthForbidden
def email_required(backend, details, user=None, *args, **kwargs):
if details.get('email') or user and user.email:
return
else:
raise AuthForbidden(backend)
You need to put that function before the user or social relation is created, so after social_uid should be fine.

Related

How to get the user name/id of a google account linked with Dialog flow

I have integrated google assistant with my dialogflow agent. I need to get the user who is invoking the intent.
For eg, If an user account "ABC" have access to invoke my agent via Google assistant app, on the welcome intent I have to send a response like "Welcome ABC". How do I achieve this with google assistant app is my endpoint.
Thanks in Advance.
You have two questions here: How to get the user's name and how to get their id.
The first thing to realize is that this information is considered personally identifiable information (PII), so Google doesn't give it to you without the permission of the user. How you ask for that permission, and how it is delivered to you, depends on some of your exact needs.
User ID
Historically, you could get an anonymous user ID for the Assistant account. This would be different than the Google User ID that is available below and was meant to be a persistent identifier so you could keep track of returning users.
This has been deprecated, and if this is all you need, then you can create your own identifier and save it as part of the userStorage.
Requesting user information
The traditional way of getting their name is to request the user for permission to access their information. If you're using the actions-on-google library, you do this using the Permission object with something like this:
const options = {
// We just want permission to get their name
permissions: ['NAME'],
// Prompt them why we want the information
context: 'To address you by name'
};
conv.ask(new Permission(options));
If the user grants permission, the results will be available in conv.user.name. You should save this in the userStorage, since the permission is not persistent. So this might look something like:
var userStorageStr = conv.user.userStorage || '{}';
var userStorage = JSON.parse( userStorageStr );
var name = conv.user.name || userStorage.name;
userStorage.name = name;
// ...
conv.user.userStorage = JSON.stringify( userStorage );
With the multivocal library, you would indicate that the User/Name environment property is one of the Requirements for the action or intent you want. So this might be in your configuration as
Local: {
en: {
Requirements: {
"Action.multivocal.welcome": "User/Name"
}
}
}
The name will be available in the environment under User/Name.
If you're using JSON, then you need to use the user information helper. For Dialogflow, this would be under the payload.google.systemIntent property, while for the Actions SDK this would be in expectedInputs[0].possibleIntents[0]. You might specify something like this:
{
"intent": "actions.intent.PERMISSION",
"inputValueData": {
"#type": "type.googleapis.com/google.actions.v2.PermissionValueSpec",
"optContext": "To address you by name",
"permissions": [
"NAME"
]
}
}
The name will be under the originalDetectIntentRequest.payload.user.profile field if you are using Dialogflow and user.profile for the Action SDK.
All of this seems like a lot, just to get a name. And you can't get the email address if you want that in addition. But there are other options.
Requesting their Google Profile
Their Google Profile contains both their unique Google ID, their full name (in the "name" field, given_name, last_name, and typically some other information such as their email address (the email address isn't guaranteed since they can omit this from their profile, but is typically there). You would use Google Sign-In for the Assistant to request this information. There is some configuration required in the Action console, and then you would request permission to get it using the sign-in helper.
With the actions-on-google library, the line would be something like:
conv.ask(new SignIn());
Once the user granted it, you can get their profile in
conv.user.profile.payload
their name in
conv.user.profile.payload.name
and their email in, you guessed it,
conv.user.profile.payload.email
Note that unlike asking for the user information, the profile will be available in all future activity with you. You don't need to store it.
With multivocal, you would say that the User/IsAuthenticated environment setting is one of the Requirements for the action or intent you want. So this might be in your configuration as
Local: {
en: {
Requirements: {
"Action.multivocal.welcome": "User/IsAuthenticated"
}
}
}
The profile will be available in the environment under User/Profile, the name would be in User/Profile/name, and the email in User/Profile/email.
If you're using JSON, then you need to use the sign-in helper. For Dialogflow, this would be under the payload.google.systemIntent property, while for the Actions SDK this would be in expectedInputs[0].possibleIntents[0]. You might specify something like this:
{
"intent": "actions.intent.SIGN_IN",
"inputValueData": {}
}
You will get an identity token for the user in the originalDetectIntentRequest.payload.user.idToken field if you are using Dialogflow and user.idToken for the Action SDK. You will need to validate and decode this JWT. (The actions-on-google and multivocal libraries handle this step for you.)
The easiest would be to use Google Sign-In for the Assistant: https://developers.google.com/actions/identity/google-sign-in

Google Sign-In with Passportjs not getting authenticated

I'm using Sails with Passport for authentication. I'm using passport-google-oauth(OAuth2Strategy) and passport-facebook for enabling Google Sign-in.
I'm not too well-versed with Passport, so pardon me if this is a rookie question. I've set up login via Facebook and it works just fine. With Google, I do receive an authorization code after allowing access to the app, but the I'm eventually not authenticated. I'm guessing the same code should work for both Facebook and Google since the strategies are both based on oauth2.
I'm not even sure what code to share, since I'm using the auto-generated code from sails-generate-auth, but do let me know if there's anything else I can share.
Any ideas on why this might be happening? The app is locally hosted but that's unlikely to be the problem since I am getting to the authorization stage anyway.
I faced the same problem and it was located here in in api/services/passport.js:
// If the profile object contains a list of emails, grab the first one and
// add it to the user.
if (profile.hasOwnProperty('emails')) {
user.email = profile.emails[0].value;
}
// If the profile object contains a username, add it to the user.
if (profile.hasOwnProperty('username')) {
user.username = profile.username;
}
// If neither an email or a username was available in the profile, we don't
// have a way of identifying the user in the future. Throw an error and let
// whoever's next in the line take care of it.
if (!user.username && !user.email) {
return next(new Error('Neither a username nor email was available'));
}
The Google service was not returning a profile.username property.
Because of it, the user is not saved in the database and cannot be authenticated. Then the passport callback receives an empty user, so the function that handles errors is fired and the user is redirected to the login page.
This change allows to use the displayName property as the username:
// If the profile object contains a list of emails, grab the first one and
// add it to the user.
if (profile.hasOwnProperty('emails')) {
user.email = profile.emails[0].value;
}
// If the profile object contains a username, add it to the user.
if (profile.hasOwnProperty('username')) {
user.username = profile.username;
}
/** Content not generated BEGIN */
// If the username property was empty and the profile object
// contains a property "displayName", add it to the user.
if (!user.username && profile.hasOwnProperty('displayName')) {
console.log(profile); // <= Use it to check the content given by Google about the user
user.username = profile.displayName;
}
/** Content not generated END */
// If neither an email or a username was available in the profile, we don't
// have a way of identifying the user in the future. Throw an error and let
// whoever's next in the line take care of it.
if (!user.username && !user.email) {
return next(new Error('Neither a username nor email was available'));
}
You could also use the profile.id property because profile.displayName is not necessarily unique (ie: two Google accounts can have an identical displayName). But it is also true accross different services: a Twitter account could also have the same username than a Facebook account. If both register on your application, you will have a bug. This is a problem from the code generated by sails-generate-auth and you should adapt it with the behavior that you want.
I will propose a PR if this solution works for you too.
Alright, so this ultimately turned out to be a known issue with the API.
TL;DR: Enable the Google+ API and the Contacts API as mentioned here. (The Contacts API isn't required, as #AlexisN-o pointed out in the comments. My setup worked as desired with Contacts API disabled. This obviously depends on what scope you're using.)
I believe it's not a nice way of failing since this was an API error that was prevented from bubbling up. Anyway, I dug into passport.authenticate to figure out what was going wrong. This eventually calls the authenticate method defined in the package corresponding to the strategy (oauth2 in this case). In here (passport-google-oauth/lib/passport-google-oauth/oauth2.js) I found that the accessToken was indeed being fetched from Google, so things should be working. This indicated that there was a problem with the requests being made to the token urls. So I ventured a little further into passport-oauth2/lib/strategy.js and finally managed to log this error:
{ [InternalOAuthError: failed to fetch user profile]
name: 'InternalOAuthError',
message: 'failed to fetch user profile',
oauthError:
{ statusCode: 403,
data: '{
"error": {
"errors": [{
"domain": "usageLimits",
"reason": "accessNotConfigured",
"message": "Access Not Configured. The API (Google+ API) is not enabled for your project. Please use the Google Developers Console to update your configuration.",
"extendedHelp": "https://console.developers.google.com"
}],
"code": 403,
"message": "Access Not Configured. The API (Google+ API) is not enabled for your project. Please use the Google Developers Console to update your configuration."
}
}'
} }
This was the end of the hunt for me and the first result for the error search led to the correct answer. Weird fix though.

Facebook oAuth user details with MVC 4

I am using the OAuthWebSecurity with MVC to allow users of my website to login using Facebook's oAuth. Everything works fine, and I have a test user authenticating fine.
My question is based on the details Facebook can provide. I am currently returning the user details using the following...
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication();
This will give the follwing details:
UserName (email)
ProviderUserId
I also get a ExternalData object which has:
UserName
Name
Gender
Do you know if it's possible to get further data, maybe DoB, photo etc?
Pardon me for not framing me the full answer. I am just supplying a link. Please check
http://blue-and-orange.net/articles/facebook/displaying-facebook-user-data-in-aspnet-mvc-4/
Everything is explained here.
For setting permission you can go through this documentation
https://developers.facebook.com/docs/facebook-login/permissions/
if you were to request a user's email address, but never asked them for the 'email' permission, you would receive an OAuth error as show below.
try {
var client = new FacebookClient("my_access_token");
dynamic result = client.Get("me/email");
var email = (string)result.email;
}
catch (FacebookOAuthException) {
// The access token expired or the user
// has not granted your app 'email' permission.
// Handle this by redirecting the user to the
// Facebook authenticate and ask for email permission.
}

Loose req.session when trying to get more FB privileges via everyauth

I've been doing user authentication with everyauth and Facebook and all works well. Now, I want to integrate an ability to post to Facebook. Since my app asks only for email scope when users first login, I'll need to get a larger FB scope, and am trying to follow the FB guidelines and only ask for this additional scope when I need it.
I added the following code to my everyauth configuration as per the docs:
everyauth
.facebook
.appId(conf.fb.appId)
.appSecret(conf.fb.appSecret)
//TODO add custom redirect for when authentication is not approved
.scope(function (req, res) {
console.log('Setting FB scope');
console.log('Session: ' + util.inspect(req.session));
var session = req.session;
switch (session.userPhase) {
case 'share-media':
return 'email,user_status';
default:
return 'email';
}
})
All is well when an unauthenticated user logs into the application. The problem is that when I want to "up the ante" on FB scope, which I do by setting req.session.userPhase to 'share-media', and then present a link to /auth/facebook to confirm they want to allow posting to FB. When this happens, I get an error that req.session is undefined from the above code (all of req is undefined).
I assume this is since a previously logged-in user is essentially re-authenticating, but isn't that how I would get more scope from Facebook? Am I going about this the wrong way?
Thanks!!!

Making Twitter, Tastypie, Django, XAuth and iOS work to Build Django-based Access Permissions

I will build an iOS application whose functionality will be based on access permissions provided by a Django REST application.
Django manages the permissions for the activities in the iOS app. User A can do Work A if he/she is permitted. Permissions will be queried via ASIHTTPRequest to a REST API served by Django Tastypie.
There is no registration. Users will just be able to login via Twitter. XAuth will be used to present a login screen for users.
There are 2 types of users. For example purposes, there will be Type 1 and Type 2. Type 1 will be ordinary user who can only browse data in the iOS app.
Type 2 user can submit/edit data.
That's it theoretically. However...I don't know where to start!!
The biggest roadblock:
How can I hook Twitter XAuth with Django's user backend via Tastypie?
If I know this then I can query the necessary permissions.
Thanks in advance!
I've done something similar with django + tastypie and facebook login for iOS.
Authentication
Log the user in using whatever means you will, get the access_token.
Create a GET request tastypie endpoint to which you will pass the accesstoken as a query string.
On the server side validate etc... and then create your own internal "tastypie" token and return that in the response to the get request e.g:
class GetToken(ModelResource):
"""
Authenticates the user via facebook and returns an APIToken for them.
"""
class Meta(object):
queryset = ApiKey.objects.all()
resource_name = 'authenticate'
fields = ['user', 'key']
allowed_methods = ['get']
authorization = Authorization()
authentication = FacebookAuthentication()
def prepend_urls(self):
"""We override this to change default behavior
for the API when using GET to actually "create" a resource,
in this case a new session/token."""
return [
url(r"^(?P<resource_name>%s)%s$" % (self._meta.resource_name, trailing_slash()),
self.wrap_view('_create_token'), name="api_get_token"),
]
def _create_token(self, request, **kwargs):
"""Validate using FacebookAuthentication, and create Api Token if authenticated"""
self.method_check(request, allowed=['get'])
# This checks that the user is authenticated on facebook and also creates the user
# if they have not been created.
self.is_authenticated(request)
self.throttle_check(request)
bundle = self.build_bundle(obj=None, request=request)
bundle = self.obj_create(bundle, request, **kwargs)
bundle = self.full_dehydrate(bundle)
self.log_throttled_access(request)
return self.create_response(request, bundle.data)
def obj_create(self, bundle, request=None, **kwargs):
"""Create a new token for the session"""
bundle.obj, created = ApiKey.objects.get_or_create(user=request.user)
return bundle
Pass the returned API key on all subsequent calls, can either be as a query string param again or I set it on the Authorisation header for every call.
Make sure ALL the other resources you want to have authentication on have ApiKeyAuthentication() set in the Meta.
class ThingResource(ModelResource):
class Meta:
queryset = Thing.objects.all()
resource_name = 'thing'
authentication = ApiKeyAuthentication()
Authorisation
So now you know on the server side that the user is who they say they are, what is this user allowed to do? Thats what the authorisation meta is all about.
You probably want Django Authorisation in which case you can just use the normal permissioning schemes for users, or you could roll your own. It's pretty simple.
amrox has a nice example on how to hook a custom fork of django-oauth-plus that supports xAuth into tastypie. I imagine it can be tweaked to suit your purposes.