python - how - ndb properties
Administrar la autenticación de los usuarios en Google App Engine (1)
Entiendo la confusión, pero el sistema está "funcionando como se diseñó".
En cualquier momento, un manejador de GAE puede tener cero o un "usuario conectado" (el objeto devuelto por users.get_current_user()
, o None
si no hay un usuario conectado) y cero o más "tokens de autorización de oauth2" (para cualesquiera usuarios y alcances han sido otorgados y no revocados).
No hay ninguna restricción que obligue a las cosillas oauth2 a coincidir, en cualquier sentido, con el "usuario conectado, si hay alguno".
Recomiendo consultar la muestra muy simple en https://code.google.com/p/google-api-python-client/source/browse/samples/appengine/main.py (para ejecutarlo, tendrás para clonar todo el paquete "google-api-python-client", luego copie en los directorios del directorio google-api-python-client/source/browse/samples/appengine
apiclient/
y oauth2client/
de este mismo paquete y httplib2
desde https://github.com/jcgregorio/httplib2 - y también personalice client_secrets.json
- sin embargo, no necesita ejecutarlo, solo para leer y seguir el código).
Este ejemplo ni siquiera usa users.get_current_user()
: no lo necesita ni le importa: solo muestra cómo usar oauth2
, y no hay conexión entre la celebración de un token oauth2
oauth2 y el servicio del users
. . (Esto le permite, por ejemplo, que cron ejecute en nombre de uno o más usuarios determinadas tareas más adelante; cron no inicia sesión, pero no importa). Si los tokens oauth2 se almacenan y recuperan correctamente, puede usarlos. ellos).
Entonces, el código crea un decorador a partir de los secretos del cliente, con scope=''https://www.googleapis.com/auth/plus.me''
, luego usa @decorator.oauth_required
en el manejador para asegurar la autorización, y con el decorador http
autorizado, obtiene
user = service.people().get(userId=''me'').execute(http=http)
con service
creado anteriormente como discovery.build("plus", "v1", http=http)
(con un http
diferente no autorizado).
Si ejecuta esto localmente, es fácil agregar un inicio de sesión falso (recuerde, el inicio de sesión de usuario es falso con dev_appserver) para que users.get_current_user()
devuelva [email protected]
o cualquier otro correo electrónico falso que ingrese en la pantalla de inicio de sesión falso - y esto de ninguna manera impide que el flujo de oauth2
completamente separado siga funcionando según lo previsto (es decir, exactamente de la misma manera que sin un inicio de sesión falso).
Si despliega la aplicación modificada (con un inicio de sesión de usuario adicional) en producción, el inicio de sesión tendrá que ser real, pero es tan indiferente y separado de la parte oauth2
de la aplicación.
Si la lógica de su aplicación requiere restringir el token oauth2
al usuario específico que también ha iniciado sesión en su aplicación, tendrá que implementarlo usted mismo, por ejemplo, estableciendo el scope
en ''https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/plus.profile.emails.read''
(más cualquier otra cosa que necesite), obtendrá de service.people().get(userId=''me'')
a objeto del user
con (entre otras cosas) un atributo de emails
en el que puede verificar que el token de autorización es para el usuario con el correo electrónico que desea autorizar (y tomar medidas correctivas de otro modo, p. ej. mediante una URL de cierre de sesión & c). ((Esto se puede hacer de forma más sencilla y, en cualquier caso, dudo que realmente necesite dicha funcionalidad, pero solo quería mencionarla)).
Estoy trabajando en una aplicación web basada en el motor de la aplicación de Google. La aplicación utiliza las API de API de Apis. Básicamente, cada controlador se extiende desde este BaseHandler y, como primera operación de cualquier get / post, se ejecuta checkAuth.
class BaseHandler(webapp2.RequestHandler):
googleUser = None
userId = None
def checkAuth(self):
user = users.get_current_user()
self.googleUser = user;
if user:
self.userId = user.user_id()
userKey=ndb.Key(PROJECTNAME, ''rootParent'', ''Utente'', self.userId)
dbuser = MyUser.query(MyUser.key==userKey).get(keys_only=True)
if dbuser:
pass
else:
self.redirect(''/'')
else:
self.redirect(''/'')
La idea es redirigir a / si ningún usuario está conectado a través de Google O si no hay un usuario en mi base de datos de usuarios que tengan esa ID de Google.
El problema es que puedo iniciar sesión con éxito en mi aplicación web y realizar operaciones. Luego, desde Gmail, o Salga de cualquier cuenta de Google PERO si trato de seguir usando la aplicación web, funciona. Esto significa que users.get_current_user () aún devuelve un usuario válido (válido pero realmente VIEJO). ¿Es eso posible?
ACTUALIZACIÓN IMPORTANTE : Entiendo lo explicado en el comentario de Alex Martelli: existe una cookie que mantiene válida la anterior autenticación GAE. El problema es que la misma aplicación web también aprovecha la biblioteca de Google Api Client para Python https://developers.google.com/api-client-library/python/ para realizar operaciones en Drive y Calendar. En las aplicaciones de GAE, dicha biblioteca se puede utilizar fácilmente a través de decoradores que implementan todo el flujo de OAuth2 ( https://developers.google.com/api-client-library/python/guide/google_app_engine ).
Por lo tanto, mis manejadores obtienen / publican métodos decorados con oauth_required como este
class SomeHandler(BaseHandler):
@DECORATOR.oauth_required
def get(self):
super(SomeHandler,self).checkAuth()
uid = self.googleUser.user_id()
http = DECORATOR.http()
service = build(''calendar'', ''v3'')
calendar_list = service.calendarList().list(pageToken=page_token).execute(http=http)
Donde el decorador es
from oauth2client.appengine import OAuth2Decorator
DECORATOR = OAuth2Decorator(
client_id=''XXXXXX.apps.googleusercontent.com'',
client_secret=''YYYYYYY'',
scope=''https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.appdata https://www.googleapis.com/auth/drive.file''
)
Por lo general, funciona bien. Sin embargo (!!) cuando la aplicación está inactiva durante mucho tiempo sucede que el decorador de oauth2 me redirige a la página de autenticación de Google donde, si cambio de cuenta (tengo 2 cuentas diferentes) ocurre algo raro: la aplicación todavía está registrada como la cuenta anterior (recuperada a través de users.get_current_user ()) mientras que la biblioteca del cliente api, y por lo tanto el decorador oauth2, devuelve datos (unidad, calendario, etc.) pertenecientes a la segunda cuenta.
Lo cual no es REALMENTE apropiado.
Siguiendo el ejemplo anterior (clase SomeHandler) supongamos que estoy registrado como Cuenta A. Los usuarios.get_current_user () siempre devuelven A como se esperaba. Ahora supongamos que dejé de usar la aplicación, después de un tiempo largo el oauth_required me redirige a la página de la cuenta de Google. Por lo tanto, decido (o me equivoco) que el registro es como Cuenta B. Al acceder al método Obtener de la clase SomeHandler el ID de usuario (recuperado a través de users.get_current_user () es A mientras que la lista de calendarios regresa a través del objeto de servicio (Google Api) biblioteca del cliente) es la lista de calendarios que pertenecen a B (el usuario real actualmente registrado).
¿Estoy haciendo algo mal? es algo esperado?
Otra actualización
esto es después de la respuesta de Martelli. He actualizado los controladores de esta manera:
class SomeHandler(BaseHandler):
@DECORATOR.oauth_aware
def get(self):
if DECORATOR.has_credentials():
super(SomeHandler,self).checkAuth()
uid = self.googleUser.user_id()
try:
http = DECORATOR.http()
service = build(''calendar'', ''v3'')
calendar_list = service.calendarList().list(pageToken=page_token).execute(http=http)
except (AccessTokenRefreshError, appengine.InvalidXsrfTokenError):
self.redirect(users.create_logout_url(
DECORATOR.authorize_url()))
else:
self.redirect(users.create_logout_url(
DECORATOR.authorize_url()))
así que, básicamente, ahora uso oauth_aware y, en caso de que no tenga ninguna credencial, cierro la sesión del usuario y lo redirijo al DECORATOR.authorize_url ()
Me di cuenta de que después de un período de inactividad, el controlador genera excepciones AccessTokenRefreshError y appengine.InvalidXsrfTokenError (pero el método has_credentials () devuelve True). Los atrapo y (nuevamente) redirijo el flujo al cierre de sesión y autorizo_url ()
Parece funcionar y parece ser robusto para el cambio de cuentas. ¿Es una solución razonable o no estoy considerando algunos aspectos del problema?