Este é o terceiro tutorial da série de complementos da Sala de Aula.
Neste tutorial, você processa visitas repetidas ao nosso complemento recuperando automaticamente as credenciais concedidas anteriormente a um usuário. Em seguida, você encaminha os usuários para páginas em que eles podem emitir solicitações de API imediatamente. Esse é um comportamento necessário para os complementos do Google Sala de Aula.
Durante este tutorial, você vai:
- Implementar armazenamento permanente para nossas credenciais de usuário.
- Extraia e avalie o parâmetro de consulta do complemento
login_hint
. É um número de ID do Google exclusivo do usuário conectado.
Depois disso, você pode autorizar totalmente os usuários no seu app da Web e emitir chamadas para as APIs do Google.
Entender os parâmetros de consulta do iframe
O Google Sala de Aula carrega o URI de configuração de anexo do complemento ao ser
aberto. O Google Sala de Aula
adiciona vários parâmetros de consulta GET
ao URI, que contêm informações contextuais
úteis. Se, por exemplo, o URI de descoberta de anexos for
https://example.com/addon
, o Google Sala de Aula vai criar o iframe com
o URL de origem definido como
https://example.com/addon?courseId=XXX&itemId=YYY&itemType=courseWork&addOnToken=ZZZ
,
em que XXX
, YYY
e ZZZ
são IDs de string. Consulte o guia de iframes para uma
descrição detalhada desse cenário.
Há cinco parâmetros de consulta possíveis para o URL de descoberta:
courseId
: o ID do curso atual do Google Sala de Aula.itemId
: o ID do item de transmissão que o usuário está editando ou criando.itemType
: o tipo de item de transmissão que o usuário está criando ou editando, um decourseWork
,courseWorkMaterial
ouannouncement
.addOnToken
: um token usado para autorizar determinadas ações de complementos do Google Sala de Aula.login_hint
: o ID do Google do usuário atual.
Este tutorial aborda login_hint
. Os usuários são direcionados com base na disponibilidade desse
parâmetro de consulta, seja para o fluxo de autorização, se estiver ausente, ou para
a página de descoberta de complementos, se estiver presente.
Acessar os parâmetros de consulta
Os parâmetros de consulta são transmitidos para o aplicativo da Web na string de URI. Armazene esses valores na sua sessão. Eles são usados no fluxo de autorização e para armazenar e recuperar informações sobre o usuário. Esses parâmetros de consulta são transmitidos apenas quando o complemento é aberto pela primeira vez.
Python
Navegue até as definições das rotas do Flask (routes.py
se você estiver
seguindo nosso exemplo fornecido). Na parte de cima do caminho de destino do complemento
(/classroom-addon
no exemplo fornecido), recupere e armazene o
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")
Verifique se login_hint
(se presente) está armazenado na sessão. Esse é um
local adequado para armazenar esses valores. Eles são temporários, e você recebe
novos valores quando o complemento é aberto.
# 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
Navegue até a rota de destino do complemento na classe do controlador
(/addon-discovery
em AuthController.java
no exemplo fornecido). No
início desta rota, extraia e armazene o 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");
Verifique se login_hint
(se presente) está armazenado na sessão. Esse é um
local adequado para armazenar esses valores. Eles são temporários, e você recebe
novos valores quando o complemento é aberto.
/** 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);
}
Adicionar os parâmetros de consulta ao fluxo de autorização
O parâmetro login_hint
também precisa ser transmitido aos servidores de autenticação do Google. Isso facilita o processo de autenticação. Se o aplicativo souber
qual usuário está tentando fazer a autenticação, o servidor vai usar a dica para simplificar o
fluxo de login preenchendo o campo de e-mail no formulário de login.
Python
Navegue até a rota de autorização no arquivo do servidor Flask (/authorize
no exemplo fornecido). Adicione o argumento login_hint
à chamada para
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
Navegue até o método authorize()
na classe AuthService.java
. Adicione
login_hint
como um parâmetro ao método e adicione login_hint
e o argumento ao criador de URL de autorização.
String authUrl = flow
.newAuthorizationUrl()
.setState(state)
.set("login_hint", login_hint)
.setRedirectUri(REDIRECT_URI)
.build();
Adicionar armazenamento permanente para credenciais do usuário
Se você receber login_hint
como um parâmetro de consulta quando o complemento for carregado, isso indica
que o usuário já concluiu o fluxo de autorização para nosso
aplicativo. Você precisa recuperar as credenciais anteriores em vez de forçar
os usuários a fazer login novamente.
Você recebeu um token de atualização após a conclusão do fluxo de autorização. Salve esse token. Ele será reutilizado para receber um token de acesso, que é de curta duração e necessário para usar as APIs do Google. Você salvou essas credenciais na sessão, mas precisa armazená-las para processar visitas repetidas.
Definir o esquema de usuário e configurar o banco de dados
Configurar um esquema de banco de dados para um User
.
Python
Definir o esquema de usuário
Um User
contém os seguintes atributos:
id
: o ID do usuário do Google. Ele precisa corresponder aos valores fornecidos no parâmetro de consultalogin_hint
.display_name
: o nome e o sobrenome do usuário, como "Alex Smith".email
: o endereço de e-mail do usuário.portrait_url
: o URL da foto do perfil do usuário.refresh_token
: o token de atualização adquirido anteriormente.
Este exemplo implementa o armazenamento usando o SQLite, que tem suporte nativo do
Python. Ele usa o módulo flask_sqlalchemy
para facilitar o gerenciamento do banco de dados.
Configurar o banco de dados
Primeiro, especifique um local de arquivo para o banco de dados. Navegue até o arquivo de configuração do servidor (config.py
no exemplo fornecido) e adicione o seguinte.
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
Isso aponta o Flask para o arquivo data.sqlite
no mesmo diretório que o
main.py
.
Em seguida, navegue até o diretório do módulo e crie um novo arquivo models.py
.
Isso é webapp/models.py
se você estiver seguindo nosso exemplo. Adicione
o código abaixo ao novo arquivo para definir a tabela User
, substituindo o
nome do módulo por webapp
, se for 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 fim, no arquivo __init__.py
do módulo, adicione o seguinte para importar
os novos modelos e criar o banco de dados.
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
Definir o esquema de usuário
Um User
contém os seguintes atributos:
id
: o ID do usuário do Google. Esse valor precisa corresponder ao valor fornecido no parâmetro de consultalogin_hint
.email
: o endereço de e-mail do usuário.
Crie um arquivo schema.sql
no diretório resources
do módulo. O Spring
lê esse arquivo e gera um esquema para o banco de dados.
Defina a tabela com um nome, users
, e colunas para representar os atributos User
, id
e email
.
CREATE TABLE IF NOT EXISTS users (
id VARCHAR(255) PRIMARY KEY, -- user's unique Google ID
email VARCHAR(255), -- user's email address
);
Crie uma classe Java para definir o modelo User
do banco de dados. Isso é
User.java
no exemplo fornecido.
Adicione a anotação @Entity
para indicar que este é um POJO que pode ser
salvo no banco de dados. Adicione a anotação @Table
com o
nome da tabela correspondente que você configurou em schema.sql
.
O exemplo de código inclui construtores e setters para os dois
atributos. O construtor e os setters são usados em
AuthController.java
para criar ou atualizar um usuário no banco de dados. Você
também pode incluir getters e um método toString
conforme achar melhor, mas, para
este tutorial específico, esses métodos não são usados e são omitidos do
exemplo de código nesta página para encurtar.
/** 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; }
}
Crie uma interface chamada UserRepository.java
para processar operações CRUD
no banco de dados. Essa interface estende a interface CrudRepository
.
/** Provides CRUD operations for the User class by extending the
* CrudRepository interface. */
@Repository
public interface UserRepository extends CrudRepository<User, String> {
}
A classe controller facilita a comunicação entre o cliente e o
repositório. Portanto, atualize o construtor da classe do controlador para injetar
a classe 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;
}
Configurar o banco de dados
Para armazenar informações relacionadas ao usuário, use um banco de dados H2 com suporte
inerente ao Spring Boot. Esse banco de dados também é usado em tutoriais
subsequentes para armazenar outras informações
relacionadas ao Google Sala de Aula. A configuração do banco de dados H2 requer a adição da seguinte
configuração 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
A configuração spring.datasource.url
cria um diretório, chamado h2
, com
o arquivo userdb
armazenado nele. Adicione o caminho para o banco de dados H2 ao
.gitignore
. É necessário atualizar o spring.datasource.username
e o
spring.datasource.password
antes de executar o aplicativo para definir o
banco de dados com um nome de usuário e uma senha de sua escolha. Para atualizar o
nome de usuário e a senha do banco de dados após a execução do aplicativo,
exclua o diretório h2
gerado, atualize a configuração e
execute o aplicativo novamente.
Definir a configuração spring.jpa.hibernate.ddl-auto
como update
garante que
os dados armazenados no banco de dados sejam preservados quando o aplicativo for reiniciado.
Para limpar o banco de dados sempre que o aplicativo for reiniciado, defina essa
configuração como create
.
Defina a configuração spring.jpa.open-in-view
como false
. Essa configuração é ativada
por padrão e pode resultar em problemas de desempenho que são
difíceis de diagnosticar na produção.
Como descrito anteriormente, você precisa recuperar as credenciais de um
usuário recorrente. Isso é facilitado pelo suporte à loja de credenciais
integrada oferecida pelo GoogleAuthorizationCodeFlow
.
Na classe AuthService.java
, defina um caminho para o arquivo em que a
classe de credencial está armazenada. Neste exemplo, o arquivo é criado no
diretório /credentialStore
. Adicione o caminho para a loja de credenciais ao
.gitignore
. Esse diretório é gerado quando o usuário inicia o
fluxo de autorização.
private static final File dataDirectory = new File("credentialStore");
Em seguida, crie um método no arquivo AuthService.java
que crie e
retorne um objeto FileDataStoreFactory
. Esse é o repositório de dados que armazena credenciais.
/** 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;
}
Atualize o método getFlow()
em AuthService.java
para incluir
setDataStoreFactory
no método
GoogleAuthorizationCodeFlow Builder()
e chame getCredentialDataStore()
para definir o repositório de dados.
GoogleAuthorizationCodeFlow authorizationCodeFlow =
new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT,
JSON_FACTORY,
getClientSecrets(),
getScopes())
.setAccessType("offline")
.setDataStoreFactory(getCredentialDataStore())
.build();
Em seguida, atualize o método getAndSaveCredentials(String authorizationCode)
.
Antes, esse método conseguia credenciais sem armazená-las
em nenhum lugar. Atualize o método para armazenar as credenciais no armazenamento de dados
indexado pelo ID do usuário.
O ID do usuário pode ser obtido do objeto TokenResponse
usando o
id_token
, mas ele precisa ser verificado primeiro. Caso contrário, os aplicativos
de cliente poderão falsificar a identidade de usuários enviando IDs
modificados para o servidor. É recomendável usar as bibliotecas de cliente
da API do Google para validar o id_token
. Consulte a [página de identidade do Google sobre
como verificar o token de ID do Google] para mais informações.
// 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.");
}
Depois que o id_token
for verificado, obtenha o userId
para armazenar junto
com as credenciais recebidas.
// Obtain the user id from the id_token.
Payload payload = idToken.getPayload();
String userId = payload.getSubject();
Atualize a chamada para flow.createAndStoreCredential
para incluir userId
.
// Save the user id and credentials to the configured FileDataStoreFactory.
Credential credential = flow.createAndStoreCredential(tokenResponse, userId);
Adicione um método à classe AuthService.java
que retorne as credenciais
de um usuário específico, se ele existir no repositório de dados.
/** 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;
}
}
Recuperar credenciais
Defina um método para buscar Users
. Você recebe um id
no parâmetro de consulta login_hint
, que pode ser usado para recuperar um registro de usuário 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
Na classe AuthController.java
, defina um método para extrair um usuário do
banco de dados com base no ID do usuário.
/** 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;
}
Armazenar credenciais
Há dois cenários ao armazenar credenciais. Se o id
do usuário já estiver
no banco de dados, atualize o registro com novos valores. Caso contrário,
crie um novo registro User
e adicione ao banco de dados.
Python
Primeiro, defina um método utilitário que implemente o comportamento de armazenamento ou de atualização.
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()
Há duas instâncias em que você pode salvar credenciais no
banco de dados: quando o usuário retorna ao seu aplicativo no final do
fluxo de autorização e ao emitir uma chamada de API. É aqui que definimos anteriormente a chave credentials
da sessão.
Chame save_user_credentials
no final da rota callback
. Mantenha o
objeto user_info
em vez de extrair apenas o nome do usuário.
# 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)
Também é necessário atualizar as credenciais após as chamadas para a API. Nesse
caso, é possível fornecer as credenciais atualizadas como argumentos para o
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
Primeiro, defina um método que armazene ou atualize um objeto User
no banco de dados
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);
}
}
Há duas instâncias em que você pode salvar credenciais no
banco de dados: quando o usuário retorna ao seu aplicativo no final do
fluxo de autorização e ao emitir uma chamada de API. É aqui que definimos anteriormente a chave credentials
da sessão.
Chame saveUser
no final da rota /callback
. Mantenha o
objeto user_info
em vez de apenas extrair o e-mail do usuário.
/** 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);
Também é necessário atualizar as credenciais após as chamadas para a API. Nesse
caso, é possível fornecer as credenciais atualizadas como argumentos para o método
saveUser
.
/** Save credentials in case access token was refreshed. */
saveUser(credentials, null, session);
Credenciais expiradas
Há alguns motivos para que os tokens de atualização possam ficar inválidos. Veja alguns exemplos:
- O token de atualização não foi usado há seis meses.
- O usuário revoga as permissões de acesso do app.
- O usuário muda as senhas.
- O usuário pertence a uma organização do Google Cloud que tem políticas de controle de sessão em vigor.
Adquira novos tokens enviando o usuário novamente pelo fluxo de autorização se as credenciais dele se tornarem inválidas.
Encaminhar o usuário automaticamente
Modifique a rota de destino do complemento para detectar se o usuário já autorizou nosso aplicativo. Se sim, encaminhe para nossa página principal de complementos. Caso contrário, peça para que façam login.
Python
Verifique se o arquivo do banco de dados foi criado quando o aplicativo
foi iniciado. Insira o seguinte em um inicializador de módulo (como
webapp/__init__.py
no exemplo fornecido) ou no método principal que
inicializa o servidor.
# Initialize the database file if not created.
if not os.path.exists(DATABASE_FILE_NAME):
db.create_all()
Seu método precisa processar o parâmetro de consulta login_hint
conforme
discutido acima. Em seguida, carregue as credenciais da loja se for um visitante
recorrente. Você sabe que é um visitante recorrente se recebeu login_hint
.
Extraia as credenciais armazenadas para esse usuário e carregue-as na
sessão.
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 fim, redirecione o usuário para a página de login se não tivermos as credenciais dele. Se sim, redirecione para a página principal do 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
Navegue até a rota de destino do complemento (/addon-discovery
no exemplo fornecido). Como discutido acima, foi aqui que você processou o parâmetro de consulta login_hint
.
Primeiro, verifique se há credenciais na sessão. Caso contrário, encaminhe o
usuário pelo fluxo de autenticação chamando o método startAuthFlow
.
/** 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);
}
Em seguida, carregue o usuário do banco de dados H2 se ele for um visitante recorrente. É
um visitante recorrente se você receber o parâmetro de consulta login_hint
. Se o
usuário existir no banco de dados H2, carregue as credenciais do armazenamento de
credenciais configurado anteriormente e defina as credenciais na sessão. Se as
credenciais não tiverem sido obtidas do repositório de credenciais, encaminhe o usuário
pelo fluxo de autenticação chamando startAuthFlow
.
/** 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 fim, direcione o usuário para a página de destino do complemento.
/** Finally, if there are credentials in the session and in persistent
* storage, direct the user to the addon-discovery page. */
return "addon-discovery";
Testar o complemento
Faça login no Google Sala de Aula como um dos seus usuários de teste Professor. Acesse a guia Atividades e crie uma nova Atividade. Clique no botão Complementos abaixo da área de texto e selecione o complemento. O iframe é aberto e o complemento carrega o URI de configuração de anexo que você especificou na página Configuração do app do SDK do Google Workspace Marketplace.
Parabéns! Você já pode prosseguir para a próxima etapa: criar anexos e identificar a função do usuário.