I currently use the "Google Accounts API" to allow users to login to my GAE app. So I use users.create_login_url and users.get_current_user and add an ndb.UserProperty to my own user entity so that I can retrieve data for that user.
I'm now in the process of switching to oauth2 (using authomatic).
I need to convert all of my existing user accounts to oauth2 and I'd like to make this as easy as possible for my users. This is my current plan:
Change the login from users service to oauth2.
After the user logs in, it will look like a new account and the user will not see his or her previous data.
I'll add a prominent message asking the user to login with the old users service.
I'll then merge the old users service account with the oauth2 account.
This should work, but it will be a little confusing for the users. Is there a better way of doing this?
I'll explain how I ended up doing this in case it helps others.
I call my users managers and I have a Manager entity for each user:
class Manager(ndb.Model):
user_account = ndb.StructuredProperty(UserAccount))
linked = ndb.BooleanProperty(default=False)
user = ndb.UserProperty()
The user property is the old users service account that I will get rid of. The user_account property stores info to identify the Oauth2 account:
class UserAccount(ndb.Model):
provider = ndb.StringProperty(required=True)
id = ndb.StringProperty(required=True)
name = ndb.StringProperty()
email = ndb.StringProperty()
Essentially, for each manager, I want to set a value for user_account (Oauth2 login) and remove user (old user account). I want to do this with minimum burden on the manager.
When the user has recently logged in under the old user account, that cookie will sill be active. Now, however, the user is logging in with an Oauth2 account. After logging in with Oauth2, we check to see if the old user account cookie is still active. If so, we merge the accounts automatically. Here is a sketch of the handler.
class ManagerPage(webapp2.RequestHandler):
def get(self):
# This returns a Manager entity after the user has logged in with
# Oauth2. If the user is logging in for the first time, this will
# be a blank Manager entity.
self.get_manager()
# Temporary processing to link accounts. If the user is still logged
# as a Google user (because that cookie hasn't expired), then we
# automatically transfer their old information to the new Manager
# entity. In doing the conversion below, manager.linked is set to
# True so this can't happen more than once. Now that the Manager
# entity has been updated, redirect back to the same page.
gae_user = users.get_current_user()
if not manager.linked and gae_user:
manager.convert_old_manager(gae_user)
self.redirect("/manager")
# Present info to the manager
...
template = JINJA_ENVIRONMENT.get_template("manager.html")
self.response.write(template.render(template_values))
If the old user account cookie is not active, then I have a link in the above manager page that asks the user to link the old account with the new account. When the user logs in with the old account, they are redirected to the above Manager Page, and the account is automatically linked.
Related
I'm trying to implement social auth with google-oauth2. I almost implemented it, but I'm confused about first-time social visitor scenario:
Suppose the social user visited my site for the first time and attempted to authenticate with Google. In this case he won't be logged in, but only registered on my website.
In order to log in this user has to click on Google authentication button the second time, and now, when he has already registered on my system the logging in will be successful.
Questions:
Is it a standard routine for the first time visitors?
Is it possible to log the social user on the first attempt?
I'm using the following social auth pipeline:
SOCIAL_AUTH_PIPELINE = (
# Get the information we can about the user and return it in a simple
# format to create the user instance later. In some cases the details are
# already part of the auth response from the provider, but sometimes this
# could hit a provider API.
'social_core.pipeline.social_auth.social_details',
# Get the social uid from whichever service we're authing thru. The uid is
# the unique identifier of the given user in the provider.
'social_core.pipeline.social_auth.social_uid',
# Verifies that the current auth process is valid within the current
# project, this is where emails and domains whitelists are applied (if
# defined).
'social_core.pipeline.social_auth.auth_allowed',
# Checks if the current social-account is already associated in the site.
'social_core.pipeline.social_auth.social_user',
# Associates the current social details with another user account with
# a similar email address. Disabled by default.
'social_core.pipeline.social_auth.associate_by_email',
# Send a validation email to the user to verify its email address.
# Disabled by default.
# 'social_core.pipeline.mail.mail_validation',
# Make up a username for this person, appends a random string at the end if
# there's any collision.
'social_core.pipeline.user.get_username',
# Create a user account if we haven't found one yet.
'social_core.pipeline.user.create_user',
# Create the record that associates the social account with the user.
'social_core.pipeline.social_auth.associate_user',
# Populate the extra_data field in the social record with the values
# specified by settings (and the default ones like access_token, etc).
'social_core.pipeline.social_auth.load_extra_data',
# Update the user record with any changed info from the auth service.
'social_core.pipeline.user.user_details',
)
Thank you in advance for advises, answers, and comments!
As part of an academic project I need to scrape the data from a Facebook page of which I am not an admin, I did some research and I found that this is possible via Facebook Developper but that in the case where I am the admin of the page. If you have any suggestions that will help me in my case, I will be grateful. Thank you
use :User Profile API:
Available Profile Fields
developers.facebook.com
By default, your apps may retrieve the id, name, first_name,
last_name, and profile_pic fields for user's that have made this
information public and have opted-in to your Page.
On This Page
User Profile API
The User Profile API allows you to use a Page-scoped ID (PSID) to retrieve user profile information that can be used to personalize the experience of people interacting with your Messenger.
Contents
Availability
User Opt-in
Profile Unavailable
Available Profile Fields
Retrieving a Person's Profile
Last Ad Referral
Error Codes
Availability
Though you may have a PSID, you may not have permission or be able to retrieve a person's profile information. For example Instant Game Pages are not allowed to use this API.
User Opt-in
The following events will authorize your Messenger bot to access a person's profile information:
The person starts the conversation via a welcome screen and tapped the "Get Started" button.
The person starts the conversation by clicking a "Send to Messenger" button.
The person starts the conversation by sending a message.
The person starts the conversation by accepting a Page's message request.
Your Messenger bot uses the askPermission() function of the Messenger Extensions SDK in the webview to ask for the user_profile permission.
Some entry points allow you or people to initiate a conversation without giving your bot authorization to the person's public profile. In those cases, your Messenger bot will be granted permission to access user profile after the user replied to the initial message. Notable situations where a person may initiate a conversation with your Messenger bot, but not authorize profile permission include the following:
Conversations started via the Checkbox Plugin and the user did has not responded on Messenger.
Interactions with Ads that Click to Messenger before the user replies on Messenger
Profile Unavailable
Currently, the User Profile API does not support retrieving profile information for Messenger accounts that were created using a phone number instead of a Facebook account.
In this case, the API will return error code 2018218 with the message 'No profile available for this user.'
Available Profile Fields
By default, your apps may retrieve the id, name, first_name, last_name, and profile_pic fields for user's that have made this information public and have opted-in to your Page.
Apps can query via the API, which fields are available for a given Page. See Feature Review API
The following profile fields can be requested:
Field Name Description Feature Access
id
The user's PSID
Granted by default
name
The user's first and last name
Granted by default
first_name
First name
Granted by default
last_name
Last name
Granted by default
profile_pic
URL to the Profile picture. The URL will expire.
Granted by default
locale
Locale of the user on Facebook. For supported locale codes, see Supported Locales.
This Page level feature needs approval by Facebook
timezone
Timezone, number relative to GMT
This Page level feature needs approval by Facebook
gender
Gender
This Page level feature needs approval by Facebook
Requesting feature access to user fields for the Page
Go to Page Settings > Advanced Messaging
Under 'Info About People' select the field and click the 'Request' button.
ref:https://developers.facebook.com/docs/messenger-platform/identity/user-profile/
I'd like to implement phone verification with pyotp in my view class-based Django (2.5) project.
After new users sign up (specifying name, phone, e-mail and password) in RegisterView, they should be redirected to GetAccessCodeView with an input field for verification code and a hidden field with a secure token. For generating and sending the code and the token I have to pass there a newly created user instanse from RegisterView to GetAccessCodeView.
How can I do that? Currently newly created users have is_active field set to False (it should become True after code succesful verification), thus cannot be authorized by default, so without changing login procedure, it is impossible to use request.user directly. But if I let inactive users to log in, then all the login_required views will let unconfirmed users to open corresponding pages. Should I write is_active check for each view manually or maybe Django has some ready stuff like 'login_and_active_required'? Or maybe there is some different solution?
I need to save settings for each user on my application.
I tried to use the user as a parent of my settings object, but I cannot do this since users do not have keys.
I then created an instance in my settings object that has a reference to user property, but in the docs it says UserProperty is unstable if the user changes their email address.
I then decided to save the user_id in a StringProperty() but if the user logs in with OpenId, the user_id element is None.
Is there a way to tie the user settings to the user object that works for both google accounts and open_id accounts?
Couldn't you add a wrapper class around the google account / open_id account, so you can use the parent relationship? Something like the following:
UserWrapper(db.Model):
user = db.UserProperty()
UserSettings(db.Model):
...
# New User
user = users.get_current_user()
new_user_settings = UserSettings(...)
new_user_wrapper = UserWrapper(key=user.nickname(),
parent=new_user_settings,
user=user)
# Login Existing User
user = users.get_current_user()
user_wrapper = UserWrapper.get_by_key_name(user.nickname())
user_settings = user_wrapper.parent()
If the user wants to change their email address, look up the UserSettings with the old email, delete the associated UserWrapper, create a new UserWrapper for the new email address and associate with the old UserSettings.
Note I've made the UserSettings a parent of UserWrapper, in case associating multiple email addresses with the same account may be something of interest.
i have an application where we allow users to use Oauth2 for authentication and even Custom User Registrations. All the Users are saved into the default User entity in the datastore. If the user is logging in using Oauth2 for the first time a new record in the default User entity is created like this:
"""Check if user is already logged in"""
if self.logged_in:
logging.info('User Already Logged In. Updating User Login Information')
u = self.current_user
u.auth_ids.append(auth_id)
u.populate(**self._to_user_model_attrs(data, self.USER_ATTRS[provider]))
u.put()
else:
"""Create a New User"""
logging.info('Creating a New User')
ok, user = self.auth.store.user_model.create_user(auth_id, **self._to_user_model_attrs(data, self.USER_ATTRS[provider]))
if ok:
self.auth.set_session(
self.auth.store.user_to_dict(user)
)
self.redirect(continue_url)
for custom registrations records are inserted through the following handler.
class RegistrationHandler(TemplateHandler, SimpleAuthHandler):
def get(self):
self.render('register.html')
def post(self):
"""Process registration form."""
user = 'appname:%s' % self.request.get('email')
name = '%s %s' % (self.request.get('first_name'), self.request.get('last_name'))
password = self.request.get('password')
avatar = self.request.get('avatar')
act_url = user_activation.Activate(self.request.get('first_name'), self.request.get('email'))
ok, user = User.create_user(auth_id=user, name=name, password_raw=password, email=self.request.get('email'))
if ok:
self.auth.set_session(self.auth.store.user_to_dict(user))
acc = models.Account(display_name=self.request.get('first_name'), act_url=act_url, act_key=act_url.split('activate/')[1], user=users.User(User.get_by_auth_id(self.current_user.auth_ids[0]).email))
acc.put()
if avatar:
avt = models.Picture(is_avatar=True, is_approved=True, image=avatar, user=users.User(User.get_by_auth_id(self.current_user.auth_ids[0]).email))
avt.put()
self.redirect('/')
Now we are using webapp2_extras.sessions for session handling. We have different models like, Comments, Images, Reviews etc in which we want to use db.UserProperty() as the author field. However, the author field shows blank or None whenever we enter a record into any of these models using 'users.get_current_user()'. I think this is because we are handling the sessions through webapp2 sessions.
What we want to achieve is to be able to use the db.UserProperty field in various models and link appropriately to the current user using webapp2 sessions ?
the UserProperty() has to be passed with a User Object in order for it to properly insert the records. Even though we are able to enter the records using the following code :
user = users.User(User.get_by_auth_id(self.current_user.auth_ids[0]).email)
or
user = users.User(User.get_by_auth_id(self.current_user.auth_ids[0]).name)
but then we are not able to get the whole user object by referencing to model.author
Any ideas how we should achieve this ?
OAuth 2.0 is not currently supported by Users service. Supported options are
Google Accounts
OpenId
OAuth 1.0
I don't frankly understand what you're trying to accomplish with introducing db.User in to the codebase. Given there's self.current_user, I assume you're already handling authentication process.
When you do self.auth.store.user_model.create_user - that already gives you a webapp2's user object/entity (it has nothing to do with db.User though). I believe that's what you'll have to use as your author field given OAuth 2.0 constraint.
users.get_current_user() relies on a special cookie (App Engine internal). In fact, it has nothing to do with webapp2's session (or any other "custom" session for that matter). You could hack it by setting the cookie to a value that App Engine internals can understand and be tricked as if a user were logged in with one of the methods I mentioned, but I wouldn't recommend this approach. It is not documented (cookie name, format, etc.) and might be changed at any time.
Instead of using UserProperty to store references to the webapp2 user objects, you should instead store the auth_id as a StringProperty and add a convenience method for fetching the corresponding webapp2 user entity.
Something like this
from webapp2_extras.appengine.auth.models import User
class Comment(db.model):
text = db.StringProperty()
author = db.StringProperty()
def get_author(self):
return User.get_by_auth_id(self.author)