Esta es la tercera explicación de la serie de instructivos de complementos de Classroom.
En esta explicación, controlarás las visitas repetidas a nuestro complemento recuperando automáticamente las credenciales otorgadas anteriormente a un usuario. Luego, diriges a los usuarios a páginas desde las que pueden emitir solicitudes a la API de inmediato. Este es un comportamiento obligatorio para los complementos de Classroom.
En el transcurso de esta explicación, completarás las siguientes tareas:
- Implementamos el almacenamiento persistente para nuestras credenciales de usuario.
- Recupera y evalúa el parámetro de consulta del complemento
login_hint
. Este es un número de ID de Google único del usuario que accedió.
Una vez que termines, podrás autorizar por completo a los usuarios en tu app web y realizar llamadas a las APIs de Google.
Información sobre los parámetros de consulta de iframe
Classroom carga el URI de configuración de archivos adjuntos de tu complemento cuando se abre. Classroom agrega varios parámetros de consulta GET
al URI, que contienen información contextual útil. Si, por ejemplo, el URI de detección de archivos adjuntos es https://example.com/addon
, Classroom crea el iframe con la URL de origen configurada como https://example.com/addon?courseId=XXX&itemId=YYY&itemType=courseWork&addOnToken=ZZZ
, en la que XXX
, YYY
y ZZZ
son IDs de cadena. Consulta la guía de iframes para obtener una descripción detallada de esta situación.
Existen cinco parámetros de consulta posibles para la URL de descubrimiento:
courseId
: Es el ID del curso actual de Classroom.itemId
: Es el ID del elemento de flujo que el usuario está editando o creando.itemType
: Es el tipo de elemento de flujo que el usuario crea o edita, ya seacourseWork
,courseWorkMaterial
oannouncement
.addOnToken
: Es un token que se usa para autorizar ciertas acciones de los complementos de Classroom.login_hint
: Es el ID de Google del usuario actual.
En esta explicación, se aborda login_hint
. Los usuarios se dirigen según si se proporciona este
parámetro de consulta, ya sea al flujo de autorización si falta o a la
página de descubrimiento de complementos si está presente.
Accede a los parámetros de consulta
Los parámetros de consulta se pasan a tu aplicación web en la cadena del URI. Almacena estos valores en tu sesión. Se usan en el flujo de autorización y para almacenar y recuperar información sobre el usuario. Estos parámetros de consulta solo se pasan cuando se abre el complemento por primera vez.
Python
Navega a las definiciones de las rutas de Flask (routes.py
si sigues el ejemplo que proporcionamos). En la parte superior de la ruta de destino del complemento
(/classroom-addon
en el ejemplo que proporcionamos), recupera y almacena el
parámetro de consulta login_hint
:
# If the login_hint query parameter is available, we'll store it in the session.
if flask.request.args.get("login_hint"):
flask.session["login_hint"] = flask.request.args.get("login_hint")
Asegúrate de que login_hint
(si está presente) esté almacenado en la sesión. Este es un lugar apropiado para almacenar estos valores, ya que son efímeros y recibes valores nuevos cuando se abre el complemento.
# It's possible that we might return to this route later, in which case the
# parameters will not be passed in. Instead, use the values cached in the
# session.
login_hint = flask.session.get("login_hint")
# If there's still no login_hint query parameter, this must be their first
# time signing in, so send the user to the sign in page.
if login_hint is None:
return start_auth_flow()
Java
Navega a la ruta de destino del complemento en tu clase de controlador (/addon-discovery
en AuthController.java
en el ejemplo proporcionado). Al comienzo de esta ruta, recupera y almacena el parámetro de consulta login_hint
.
/** Retrieve the login_hint query parameter from the request URL if present. */
String login_hint = request.getParameter("login_hint");
Asegúrate de que login_hint
(si está presente) se almacene en la sesión. Este es un lugar apropiado para almacenar estos valores, ya que son efímeros y recibes valores nuevos cuando se abre el complemento.
/** If login_hint wasn't sent, use the values in the session. */
if (login_hint == null) {
login_hint = (String) session.getAttribute("login_hint");
}
/** If the there is still no login_hint, route the user to the authorization
* page. */
if (login_hint == null) {
return startAuthFlow(model);
}
/** If the login_hint query parameter is provided, add it to the session. */
else if (login_hint != null) {
session.setAttribute("login_hint", login_hint);
}
Agregar los parámetros de consulta al flujo de autorización
El parámetro login_hint
también se debe pasar a los servidores de autenticación de Google. Esto facilita el proceso de autenticación. Si tu aplicación sabe cuál es el usuario que intenta autenticarse, el servidor usa la sugerencia para simplificar el flujo de acceso completando previamente el campo de correo electrónico en el formulario de acceso.
Python
Navega a la ruta de autorización en el archivo del servidor de Flask (/authorize
en nuestro ejemplo proporcionado). Agrega el argumento login_hint
a la llamada a flow.authorization_url
.
authorization_url, state = flow.authorization_url(
# Enable offline access so that you can refresh an access token without
# re-prompting the user for permission. Recommended for web server apps.
access_type="offline",
# Enable incremental authorization. Recommended as a best practice.
include_granted_scopes="true",
# The user will automatically be selected if we have the login_hint.
login_hint=flask.session.get("login_hint"),
Java
Navega al método authorize()
en la clase AuthService.java
. Agrega login_hint
como parámetro al método y agrega login_hint
y el argumento al compilador de URLs de autorización.
String authUrl = flow
.newAuthorizationUrl()
.setState(state)
.set("login_hint", login_hint)
.setRedirectUri(REDIRECT_URI)
.build();
Agrega almacenamiento persistente para las credenciales del usuario
Si recibes login_hint
como parámetro de consulta cuando se carga el complemento, significa que el usuario ya completó el flujo de autorización de nuestra aplicación. Debes recuperar sus credenciales anteriores en lugar de forzarlos a volver a acceder.
Recuerda que recibiste un token de actualización cuando se completó el flujo de autorización. Guarda este token, ya que se volverá a usar para obtener un token de acceso, que es de corta duración y necesario para usar las APIs de Google. Anteriormente, guardaste estas credenciales en la sesión, pero debes almacenarlas para controlar las visitas repetidas.
Define el esquema de usuario y configura la base de datos
Configura un esquema de base de datos para un User
.
Python
Define el esquema de usuario
Un User
contiene los siguientes atributos:
id
: El ID de Google del usuario. Debe coincidir con los valores proporcionados en el parámetro de consultalogin_hint
.display_name
: El nombre y apellido del usuario, como "Alex Smith".email
: La dirección de correo electrónico del usuario.portrait_url
: Es la URL de la foto de perfil del usuario.refresh_token
: El token de actualización adquirido anteriormente.
En este ejemplo, se implementa el almacenamiento con SQLite, que es compatible de forma nativa con Python. Usa el módulo flask_sqlalchemy
para facilitar la administración de la base de datos.
Configura la base de datos
Primero, especifica una ubicación de archivo para nuestra base de datos. Navega al archivo de configuración del servidor (config.py
en nuestro ejemplo proporcionado) y agrega lo siguiente.
import os
# Point to a database file in the project root.
DATABASE_FILE_NAME = os.path.join(
os.path.abspath(os.path.dirname(__file__)), 'data.sqlite')
class Config(object):
SQLALCHEMY_DATABASE_URI = f"sqlite:///{DATABASE_FILE_NAME}"
SQLALCHEMY_TRACK_MODIFICATIONS = False
Esto dirige a Flask al archivo data.sqlite
en el mismo directorio que el archivo main.py
.
A continuación, navega a tu directorio de módulos y crea un nuevo archivo models.py
.
Si sigues nuestro ejemplo, este es webapp/models.py
. Agrega lo siguiente al archivo nuevo para definir la tabla User
y sustituye el nombre de tu módulo por webapp
si es diferente.
from webapp import db
# Database model to represent a user.
class User(db.Model):
# The user's identifying information:
id = db.Column(db.String(120), primary_key=True)
display_name = db.Column(db.String(80))
email = db.Column(db.String(120), unique=True)
portrait_url = db.Column(db.Text())
# The user's refresh token, which will be used to obtain an access token.
# Note that refresh tokens will become invalid if:
# - The refresh token has not been used for six months.
# - The user revokes your app's access permissions.
# - The user changes passwords.
# - The user belongs to a Google Cloud organization
# that has session control policies in effect.
refresh_token = db.Column(db.Text())
Por último, en el archivo __init__.py
de tu módulo, agrega lo siguiente para importar los modelos nuevos y crear la base de datos.
from webapp import models
from os import path
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
# Initialize the database file if not created.
if not path.exists(config.DATABASE_FILE_NAME):
db.create_all()
Java
Define el esquema de usuario
Un User
contiene los siguientes atributos:
id
: Es el ID de Google del usuario. Debe coincidir con el valor proporcionado en el parámetro de consultalogin_hint
.email
: La dirección de correo electrónico del usuario.
Crea un archivo schema.sql
en el directorio resources
del módulo. Spring lee este archivo y genera un esquema para la base de datos según corresponda.
Define la tabla con un nombre, users
, y columnas para representar los atributos User
, id
y email
.
CREATE TABLE IF NOT EXISTS users (
id VARCHAR(255) PRIMARY KEY, -- user's unique Google ID
email VARCHAR(255), -- user's email address
);
Crea una clase de Java para definir el modelo User
de la base de datos. Esto es User.java
en el ejemplo proporcionado.
Agrega la anotación @Entity
para indicar que este es un POJO que se puede guardar en la base de datos. Agrega la anotación @Table
con el nombre de la tabla correspondiente que configuraste en schema.sql
.
Ten en cuenta que el ejemplo de código incluye constructores y establecedores para los dos atributos. El constructor y los set se usan en AuthController.java
para crear o actualizar un usuario en la base de datos. También puedes incluir métodos get y un método toString
según lo consideres conveniente, pero para esta explicación en particular, estos métodos no se usan y se omiten del ejemplo de código en esta página para mayor brevedad.
/** An entity class that provides a model to store user information. */
@Entity
@Table(name = "users")
public class User {
/** The user's unique Google ID. The @Id annotation specifies that this
* is the primary key. */
@Id
@Column
private String id;
/** The user's email address. */
@Column
private String email;
/** Required User class no args constructor. */
public User() {
}
/** The User class constructor that creates a User object with the
* specified parameters.
* @param id the user's unique Google ID
* @param email the user's email address
*/
public User(String id, String email) {
this.id = id;
this.email = email;
}
public void setId(String id) { this.id = id; }
public void setEmail(String email) { this.email = email; }
}
Crea una interfaz llamada UserRepository.java
para controlar las operaciones de CRUD en la base de datos. Esta interfaz extiende la interfaz CrudRepository
.
/** Provides CRUD operations for the User class by extending the
* CrudRepository interface. */
@Repository
public interface UserRepository extends CrudRepository<User, String> {
}
La clase de controlador facilita la comunicación entre el cliente y el repositorio. Por lo tanto, actualiza el constructor de clase del controlador para insertar la clase UserRepository
.
/** Declare UserRepository to be used in the Controller class constructor. */
private final UserRepository userRepository;
/**
* ...
* @param userRepository the class that interacts with User objects stored in
* persistent storage.
*/
public AuthController(AuthService authService, UserRepository userRepository) {
this.authService = authService;
this.userRepository = userRepository;
}
Configura la base de datos
Para almacenar información relacionada con el usuario, usa una base de datos H2 que sea compatible de forma intrínseca con Spring Boot. Esta base de datos también se usa en las explicaciones posteriores para almacenar otra información relacionada con Classroom. Para configurar la base de datos H2, debes agregar la siguiente configuración a application.properties
.
# Enable configuration for persistent storage using an H2 database
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:file:./h2/userdb
spring.datasource.username=<USERNAME>
spring.datasource.password=<PASSWORD>
spring.jpa.hibernate.ddl-auto=update
spring.jpa.open-in-view=false
La configuración de spring.datasource.url
crea un directorio, llamado h2
, con el archivo userdb
almacenado en él. Agrega la ruta a la base de datos de H2 a
la .gitignore
. Debes actualizar spring.datasource.username
y spring.datasource.password
antes de ejecutar la aplicación para configurar la base de datos con un nombre de usuario y una contraseña que elijas. Para actualizar el nombre de usuario y la contraseña de la base de datos después de ejecutar la aplicación, borra el directorio h2
generado, actualiza la configuración y vuelve a ejecutar la aplicación.
Configurar la configuración de spring.jpa.hibernate.ddl-auto
en update
garantiza que los datos almacenados en la base de datos se conserven cuando se reinicie la aplicación.
Para borrar la base de datos cada vez que se reinicia la aplicación, configura esta configuración en create
.
Establece la configuración de spring.jpa.open-in-view
en false
. Esta configuración está habilitada de forma predeterminada y puede causar problemas de rendimiento difíciles de diagnosticar en la producción.
Como se describió anteriormente, debes poder recuperar las credenciales de un usuario repetido. Esto se facilita gracias a la compatibilidad con el almacén de credenciales integrado que ofrece GoogleAuthorizationCodeFlow
.
En la clase AuthService.java
, define una ruta de acceso al archivo en el que se almacena la clase de credenciales. En este ejemplo, el archivo se crea en el directorio /credentialStore
. Agrega la ruta de acceso al almacén de credenciales a .gitignore
. Este directorio se genera una vez que el usuario comienza el flujo de autorización.
private static final File dataDirectory = new File("credentialStore");
A continuación, crea un método en el archivo AuthService.java
que cree y muestre un objeto FileDataStoreFactory
. Este es el almacén de datos que almacena las credenciales.
/** Creates and returns FileDataStoreFactory object to store credentials.
* @return FileDataStoreFactory dataStore used to save and obtain users ids
* mapped to Credentials.
* @throws IOException if creating the dataStore is unsuccessful.
*/
public FileDataStoreFactory getCredentialDataStore() throws IOException {
FileDataStoreFactory dataStore = new FileDataStoreFactory(dataDirectory);
return dataStore;
}
Actualiza el método getFlow()
en AuthService.java
para incluir setDataStoreFactory
en el método GoogleAuthorizationCodeFlow Builder()
y llama a getCredentialDataStore()
para configurar el almacén de datos.
GoogleAuthorizationCodeFlow authorizationCodeFlow =
new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT,
JSON_FACTORY,
getClientSecrets(),
getScopes())
.setAccessType("offline")
.setDataStoreFactory(getCredentialDataStore())
.build();
A continuación, actualiza el método getAndSaveCredentials(String authorizationCode)
.
Anteriormente, este método obtenía credenciales sin almacenarlas en ningún lugar. Actualiza el método para almacenar las credenciales en el almacén de datos
indexadas por el ID de usuario.
El ID de usuario se puede obtener del objeto TokenResponse
con id_token
, pero primero se debe verificar. De lo contrario, las aplicaciones cliente podrían suplantar la identidad de los usuarios enviando IDs de usuario modificados al servidor. Te recomendamos que uses las bibliotecas cliente de la API de Google para validar el id_token
. Consulta la [página de Identidad de Google sobre la verificación del token de ID de Google] para obtener más información.
// Obtaining the id_token will help determine which user signed in to the application.
String idTokenString = tokenResponse.get("id_token").toString();
// Validate the id_token using the GoogleIdTokenVerifier object.
GoogleIdTokenVerifier googleIdTokenVerifier = new GoogleIdTokenVerifier.Builder(
HTTP_TRANSPORT,
JSON_FACTORY)
.setAudience(Collections.singletonList(
googleClientSecrets.getWeb().getClientId()))
.build();
GoogleIdToken idToken = googleIdTokenVerifier.verify(idTokenString);
if (idToken == null) {
throw new Exception("Invalid ID token.");
}
Una vez que se haya verificado el id_token
, obtén el userId
para almacenarlo junto con las credenciales obtenidas.
// Obtain the user id from the id_token.
Payload payload = idToken.getPayload();
String userId = payload.getSubject();
Actualiza la llamada a flow.createAndStoreCredential
para incluir userId
.
// Save the user id and credentials to the configured FileDataStoreFactory.
Credential credential = flow.createAndStoreCredential(tokenResponse, userId);
Agrega un método a la clase AuthService.java
que muestre las credenciales de un usuario específico si existe en el almacén de datos.
/** Find credentials in the datastore based on a specific user id.
* @param userId key to find in the file datastore.
* @return Credential object to be returned if a matching key is found in the datastore. Null if
* the key doesn't exist.
* @throws Exception if building flow object or checking for userId key is unsuccessful. */
public Credential loadFromCredentialDataStore(String userId) throws Exception {
try {
GoogleAuthorizationCodeFlow flow = getFlow();
Credential credential = flow.loadCredential(userId);
return credential;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
Recupera credenciales
Define un método para recuperar Users
. Se te proporciona un id
en el parámetro de consulta login_hint
, que puedes usar para recuperar un registro de usuario específico.
Python
def get_credentials_from_storage(id):
"""
Retrieves credentials from the storage and returns them as a dictionary.
"""
return User.query.get(id)
Java
En la clase AuthController.java
, define un método para recuperar un usuario de la base de datos según su ID de usuario.
/** Retrieves stored credentials based on the user id.
* @param id the id of the current user
* @return User the database entry corresponding to the current user or null
* if the user doesn't exist in the database.
*/
public User getUser(String id) {
if (id != null) {
Optional<User> user = userRepository.findById(id);
if (user.isPresent()) {
return user.get();
}
}
return null;
}
Almacenar credenciales
Existen dos situaciones cuando se almacenan credenciales. Si el id
del usuario ya está en la base de datos, actualiza el registro existente con los valores nuevos. De lo contrario, crea un nuevo registro User
y agrégalo a la base de datos.
Python
Primero, define un método de utilidad que implemente el comportamiento de almacenamiento o actualización.
def save_user_credentials(credentials=None, user_info=None):
"""
Updates or adds a User to the database. A new user is added only if both
credentials and user_info are provided.
Args:
credentials: An optional Credentials object.
user_info: An optional dict containing user info returned by the
OAuth 2.0 API.
"""
existing_user = get_credentials_from_storage(
flask.session.get("login_hint"))
if existing_user:
if user_info:
existing_user.id = user_info.get("id")
existing_user.display_name = user_info.get("name")
existing_user.email = user_info.get("email")
existing_user.portrait_url = user_info.get("picture")
if credentials and credentials.refresh_token is not None:
existing_user.refresh_token = credentials.refresh_token
elif credentials and user_info:
new_user = User(id=user_info.get("id"),
display_name=user_info.get("name"),
email=user_info.get("email"),
portrait_url=user_info.get("picture"),
refresh_token=credentials.refresh_token)
db.session.add(new_user)
db.session.commit()
Existen dos instancias en las que puedes guardar credenciales en tu base de datos: cuando el usuario vuelve a tu aplicación al final del flujo de autorización y cuando se emite una llamada a la API. Aquí es donde antes establecimos la clave credentials
de sesión.
Llama a save_user_credentials
al final de tu ruta callback
. Mantén el objeto user_info
en lugar de solo extraer el nombre del usuario.
# The flow is complete! We'll use the credentials to fetch the user's info.
user_info_service = googleapiclient.discovery.build(
serviceName="oauth2", version="v2", credentials=credentials)
user_info = user_info_service.userinfo().get().execute()
flask.session["username"] = user_info.get("name")
save_user_credentials(credentials, user_info)
También debes actualizar las credenciales después de las llamadas a la API. En este caso, puedes proporcionar las credenciales actualizadas como argumentos al método save_user_credentials
.
# Save credentials in case access token was refreshed.
flask.session["credentials"] = credentials_to_dict(credentials)
save_user_credentials(credentials)
Java
Primero, define un método que almacene o actualice un objeto User
en la base de datos de H2.
/** Adds or updates a user in the database.
* @param credential the credentials object to save or update in the database.
* @param userinfo the userinfo object to save or update in the database.
* @param session the current session.
*/
public void saveUser(Credential credential, Userinfo userinfo, HttpSession session) {
User storedUser = null;
if (session != null && session.getAttribute("login_hint") != null) {
storedUser = getUser(session.getAttribute("login_hint").toString());
}
if (storedUser != null) {
if (userinfo != null) {
storedUser.setId(userinfo.getId());
storedUser.setEmail(userinfo.getEmail());
}
userRepository.save(storedUser);
} else if (credential != null && userinfo != null) {
User newUser = new User(
userinfo.getId(),
userinfo.getEmail(),
);
userRepository.save(newUser);
}
}
Hay dos instancias en las que puedes guardar credenciales en tu base de datos: cuando el usuario regresa a tu aplicación al final del flujo de autorización y cuando emite una llamada a la API. Aquí es donde antes establecimos la clave credentials
de sesión.
Llama a saveUser
al final de la ruta /callback
. Debes conservar el objeto user_info
en lugar de solo extraer el correo electrónico del usuario.
/** This is the end of the auth flow. We should save user info to the database. */
Userinfo userinfo = authService.getUserInfo(credentials);
saveUser(credentials, userinfo, session);
También debes actualizar las credenciales después de las llamadas a la API. En este caso, puedes proporcionar las credenciales actualizadas como argumentos al método saveUser
.
/** Save credentials in case access token was refreshed. */
saveUser(credentials, null, session);
Credenciales vencidas
Ten en cuenta que hay algunos motivos por los que los tokens de actualización pueden no ser válidos. Estos son algunos de ellos:
- El token de actualización no se usó durante seis meses.
- El usuario revoca los permisos de acceso de tu app.
- El usuario cambia las contraseñas.
- El usuario pertenece a una organización de Google Cloud que tiene políticas de control de sesión vigentes.
Para adquirir tokens nuevos, vuelve a enviar al usuario a través del flujo de autorización en caso de que sus credenciales no sean válidas.
Encamina automáticamente al usuario
Modifica la ruta de destino del complemento para detectar si el usuario autorizó previamente nuestra aplicación. Si es así, redirecciona a los usuarios a nuestra página principal de complementos. De lo contrario, pídele que acceda.
Python
Asegúrate de que el archivo de base de datos se haya creado cuando se inicie la aplicación. Inserta lo siguiente en un inicializador de módulo (como webapp/__init__.py
en nuestro ejemplo proporcionado) o en el método principal que inicia el servidor.
# Initialize the database file if not created.
if not os.path.exists(DATABASE_FILE_NAME):
db.create_all()
Luego, tu método debe controlar el parámetro de consulta login_hint
como se mencionó anteriormente. Luego, carga las credenciales de la tienda si se trata de un visitante repetido. Sabes que se trata de un visitante recurrente si recibiste login_hint
.
Recupera las credenciales almacenadas de este usuario y cárgalas en la sesión.
stored_credentials = get_credentials_from_storage(login_hint)
# If we have stored credentials, store them in the session.
if stored_credentials:
# Load the client secrets file contents.
client_secrets_dict = json.load(
open(CLIENT_SECRETS_FILE)).get("web")
# Update the credentials in the session.
if not flask.session.get("credentials"):
flask.session["credentials"] = {}
flask.session["credentials"] = {
"token": stored_credentials.access_token,
"refresh_token": stored_credentials.refresh_token,
"token_uri": client_secrets_dict["token_uri"],
"client_id": client_secrets_dict["client_id"],
"client_secret": client_secrets_dict["client_secret"],
"scopes": SCOPES
}
# Set the username in the session.
flask.session["username"] = stored_credentials.display_name
Por último, dirige al usuario a la página de acceso si no tenemos sus credenciales. Si es así, redirigirlo a la página principal del complemento.
if "credentials" not in flask.session or \
flask.session["credentials"]["refresh_token"] is None:
return flask.render_template("authorization.html")
return flask.render_template(
"addon-discovery.html",
message="You've reached the addon discovery page.")
Java
Navega a la ruta de destino del complemento (/addon-discovery
en el ejemplo proporcionado). Como se mencionó anteriormente, aquí es donde controlaste el parámetro de consulta login_hint
.
Primero, verifica si existen credenciales en la sesión. Si no es así, llama al método startAuthFlow
para dirigir al usuario a través del flujo de autenticación.
/** Check if the credentials exist in the session. The session could have
* been cleared when the user clicked the Sign-Out button, and the expected
* behavior after sign-out would be to display the sign-in page when the
* iframe is opened again. */
if (session.getAttribute("credentials") == null) {
return startAuthFlow(model);
}
Luego, carga el usuario desde la base de datos de H2 si se trata de un visitante recurrente. Se trata de un visitante recurrente si recibes el parámetro de consulta login_hint
. Si el usuario existe en la base de datos de H2, carga las credenciales del almacén de credenciales configurado anteriormente y configúralas en la sesión. Si las credenciales no se obtuvieron del almacén de credenciales, llama a startAuthFlow
para dirigir al usuario a través del flujo de autenticación.
/** At this point, we know that credentials exist in the session, but we
* should update the session credentials with the credentials in persistent
* storage in case they were refreshed. If the credentials in persistent
* storage are null, we should navigate the user to the authorization flow
* to obtain persisted credentials. */
User storedUser = getUser(login_hint);
if (storedUser != null) {
Credential credential = authService.loadFromCredentialDataStore(login_hint);
if (credential != null) {
session.setAttribute("credentials", credential);
} else {
return startAuthFlow(model);
}
}
Por último, dirige al usuario a la página de destino del complemento.
/** Finally, if there are credentials in the session and in persistent
* storage, direct the user to the addon-discovery page. */
return "addon-discovery";
Prueba el complemento
Accede a Google Classroom como uno de los usuarios de prueba de Profesor. Navega a la pestaña Trabajo en clase y crea una nueva Tarea. Haz clic en el botón Complementos debajo del área de texto y, luego, selecciona el complemento. Se abre el iframe y el complemento carga el URI de configuración de archivos adjuntos que especificaste en la página Configuración de la app del SDK de Google Workspace Marketplace.
¡Felicitaciones! Ya puedes continuar con el siguiente paso: crear archivos adjuntos e identificar la función del usuario.