Gestión de la autenticación de usuarios en Google App Engine

Estoy trabajando en una aplicación web basada en el motor de Google Apps. La aplicación utiliza la autenticación de google apis. Básicamente, cada controlador se extiende desde este BaseHandler y, como primera operación de cualquier obtención / publicación, se ejecuta el 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 que redirige a / si ningún usuario ha iniciado sesión a través de Google O si no hay un Usuario en mi db de usuarios que tenga esa ID de Google.

El problema es que puedo iniciar sesión correctamente en mi aplicación web y hacer operaciones. Luego, desde gmail, o Cierre la sesión desde cualquier cuenta de Google, PERO si bash seguir usando la aplicación web, funciona. Esto significa que users.get_current_user () aún devuelve un usuario válido (válido pero en realidad OLD). ¿Es eso posible?

ACTUALIZACIÓN IMPORTANTE Entiendo lo que se explica en el Comentario de Alex Martelli: Hay una cookie que mantiene la autenticación GAE anterior válida. El problema es que la misma aplicación web también explota la biblioteca de cliente de Google Api para Python https://developers.google.com/api-client-library/python/ para realizar operaciones en Drive y Calendar. En las aplicaciones de GAE, esta biblioteca se puede usar fácilmente a través de los decoradores que implementan todo el flujo de OAuth2 ( https://developers.google.com/api-client-library/python/guide/google_app_engine ).

Por lo tanto, tengo a mis controladores / métodos de envío decorados con auth_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 auth2 me redirige a la página de autenticación de Google donde, si cambio de cuenta (tengo 2 cuentas diferentes), algo de WEIRD sucede: la aplicación aún se registra como la cuenta anterior (recuperada a través de users.get_current_user ()) mientras que la biblioteca del cliente de api, y por lo tanto el decorador oauth2, devuelve datos (unidad, calendario, etc.) que pertenecen a la segunda cuenta.

Lo que REALMENTE no es apropiado.

Siguiendo el ejemplo anterior (clase SomeHandler), supongamos que estoy registrado como Cuenta A. El users.get_current_user () siempre devuelve A como se esperaba. Ahora, supongamos que dejé de usar la aplicación, después de un largo tiempo, outh_required me redirige a la página de la cuenta de Google. Por lo tanto, decido (o cometo un error) iniciar sesión como Cuenta B. Al acceder al método Get de la clase SomeHandler, el ID de usuario (obtenido a través de users.get_current_user () es A), mientras que la lista de calendarios que se devuelve a través del objeto de servicio (Google Api biblioteca de clientes) es la lista de calendarios que pertenecen a B (el usuario actual registrado actualmente).

¿Estoy haciendo algo mal? ¿Algo se espera?

Otra actualización

Esto es después de la Respuesta de Martelli. He actualizado los manejadores 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 no tener credenciales, cierre la sesión del usuario y lo redirecciono al DECORATOR.authorize_url ()

Me he dado 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 capturo y (de nuevo) redirecciono el flujo al cierre de sesión y a authorize_url ()

Parece funcionar y parece ser robusto al cambio de cuentas. ¿Es una solución razonable o no estoy considerando algunos aspectos del problema?

Entiendo la confusión, pero el sistema está “funcionando como fue diseñado”.

En cualquier momento, un controlador GAE puede tener cero o un “usuario registrado” (el objeto devuelto por users.get_current_user() , o None si no hay un usuario registrado) y cero o más “touthens de autorización auth2” (para todos los usuarios y ámbitos han sido otorgados y no revocados).

No hay ninguna restricción que obligue a las cosas oauth2 a coincidir, en ningún sentido, con el “usuario conectado, si lo hay”.

Recomendaría revisar el ejemplo muy simple en https://code.google.com/p/google-api-python-client/source/browse/samples/appengine/main.py (para ejecutarlo, tendrá para clonar todo el paquete “google-api-python-client”, luego copie en el directorio google-api-python-client/source/browse/samples/appengine directorios apiclient/ y oauth2client/ desde este mismo paquete, así como httplib2 desde https://github.com/jcgregorio/httplib2 – y también personalice client_secrets.json – sin embargo, no es necesario que lo ejecute, solo lea y siga el código).

Este ejemplo ni siquiera utiliza 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 auth2 y el servicio para users . (Esto le permite, por ejemplo, hacer que cron se ejecute en nombre de uno o más usuarios en ciertas tareas más adelante; cron no inicia sesión, pero no importa; si los touthens oauth2 se almacenan y recuperan correctamente, se pueden usar ellos).

Por lo tanto, 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 para get autorización de un manejador, y con el decorador http autorizado, lo busca

 user = service.people().get(userId='me').execute(http=http) 

con el 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 del usuario es falso con dev_appserver) para que users.get_current_user() devuelva princess@bride.com 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 como es debido (es decir, exactamente de la misma manera que lo hace sin ningún tipo de inicio de sesión falso).

Si implementa 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 igual de indiferente e independiente 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 oauth2 sesión en su aplicación, deberá implementarlo usted mismo, por ejemplo, configurando el scope en 'https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/plus.profile.emails.read' (además de lo que necesite), obtendrá de service.people().get(userId='me') a objeto de user con (entre muchas 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 lo contrario, por ejemplo, a través de una URL de cierre de sesión & c). ((Esto se puede hacer de manera más simple y, en cualquier caso, dudo que realmente necesite dicha funcionalidad, pero, solo quería mencionarlo)).