Processar logins repetidos

Este é o terceiro tutorial sobre os complementos do Google Sala de Aula série de tutoriais.

Neste tutorial, você vai gerenciar as visitas repetidas ao complemento recuperar credenciais concedidas anteriormente a um usuário. Em seguida, você encaminha os usuários páginas de onde podem emitir solicitações de API imediatamente. Este campo é obrigatório dos complementos do Google Sala de Aula.

Neste tutorial, você vai fazer o seguinte:

  • Implementar um armazenamento permanente para as credenciais de usuário.
  • Recupere e avalie o parâmetro de consulta do complemento login_hint. Esta é uma número de ID do Google exclusivo do usuário conectado.
.

Depois de terminar, você pode autorizar totalmente os usuários no seu aplicativo da Web e emitir chamadas para nas APIs do Google.

Entender os parâmetros de consulta do iframe

O Google Sala de Aula carrega o URI de configuração de anexos do seu complemento em abertura. Sala de Aula anexa vários parâmetros de consulta GET ao URI. eles contêm informações úteis informações contextuais. Se, por exemplo, o URI de descoberta de anexo for https://example.com/addon, o Google Sala de Aula cria 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 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 stream que o usuário está editando ou criando.
  • itemType: o tipo de item de stream que o usuário está criando ou editando, um dos courseWork, courseWorkMaterial ou announcement.
  • addOnToken: um token usado para autorizar determinados Ações dos 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 encaminhados com base no seguinte: parâmetro de consulta for fornecido, seja para o fluxo de autorização, se estiver ausente, ou para a página de descoberta do complemento, se houver.

Acessar os parâmetros de consulta

Os parâmetros de consulta são transmitidos ao aplicativo da Web na string URI. Armazenamento esses valores na sessão. elas são usadas no fluxo de autorização armazenar e recuperar informações sobre o usuário. Esses parâmetros de consulta transmitidos quando o complemento é aberto pela primeira vez.

Python

Acesse as definições das suas rotas Flask (routes.py se você estiver seguindo o exemplo fornecido). Na parte de cima da rota de destino do complemento (/classroom-addon em nosso 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. Esta é uma local apropriado para armazenar esses valores; eles são efêmeros, e você recebe novos valores quando o complemento for 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 sua classe de controlador (/addon-discovery em AuthController.java no exemplo fornecido). Em no início desse trajeto, extraia e armazene a 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. Esta é uma local apropriado para armazenar esses valores; eles são efêmeros, e você recebe novos valores quando o complemento for 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 precisa ser transmitido para os servidores de autenticação do Google. . Isso facilita o processo de autenticação. se o aplicativo souber qual usuário está tentando autenticar, o servidor usa a dica para simplificar a fluxo de login preenchendo previamente o campo de e-mail no formulário de login.

Python

Navegue até a rota de autorização no arquivo de servidor Flask (/authorize no nosso exemplo). 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. Adicionar login_hint como parâmetro para o método, e adicione o login_hint e argumento para o criador de URLs de autorização.

String authUrl = flow
    .newAuthorizationUrl()
    .setState(state)
    .set("login_hint", login_hint)
    .setRedirectUri(REDIRECT_URI)
    .build();

Adicionar armazenamento permanente para credenciais de usuário

Se você receber login_hint como um parâmetro de consulta quando o complemento for carregado, isso será uma indicação de que o usuário já concluiu o fluxo de autorização da para o aplicativo. Recupere as credenciais anteriores em vez de forçar para fazer login novamente.

Lembre-se de que você recebeu um token de atualização após a conclusão do fluxo de autorização. Salve esse token. reutilizá-la para receber um token de acesso; que é de curta duração e necessária para usar as APIs do Google. Você salvou anteriormente essas credenciais na sessão, mas é preciso armazená-las para para lidar com visitas repetidas.

Definir o esquema do usuário e configurar o banco de dados

Configure um esquema de banco de dados para um User.

Python

Definir o esquema do usuário

Uma User contém os seguintes atributos:

  • id: o ID do Google do usuário. Ele deve corresponder aos valores fornecidos no Parâmetro de consulta login_hint.
  • display_name: o nome e 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 em Python. Ela usa o módulo flask_sqlalchemy para facilitar nosso banco de dados. de projetos.

Configurar o banco de dados

Primeiro, especifique o local do arquivo do nosso banco de dados. Navegue até seu servidor de configuração (config.py em nosso exemplo fornecido) e adicione o seguindo.

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 do seu arquivo main.py.

Em seguida, navegue até o diretório do módulo e crie um novo arquivo models.py. Se você estiver seguindo o exemplo fornecido, ele será webapp/models.py. Adicionar o comando a seguir ao novo arquivo para definir a tabela User, substituindo seu nome do módulo para 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 código a seguir 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 do usuário

Uma User contém os seguintes atributos:

  • id: o ID do Google do usuário. Ele deve corresponder ao valor fornecido no Parâmetro de consulta login_hint.
  • email: o endereço de e-mail do usuário.
.

Crie um arquivo schema.sql no diretório resources do módulo. Primavera lê esse arquivo e gera um esquema para o banco de dados de acordo com ele. Defina a tabela com um nome de tabela, users, e as 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 para o banco de dados. Isso é User.java no exemplo fornecido.

Adicione a anotação @Entity para indicar que esse é um POJO que pode ser salva no banco de dados. Adicione a anotação @Table com o nome da tabela correspondente que você configurou em schema.sql.

Observe que o exemplo de código inclui construtores e setters para os dois atributos. O construtor e os setters são usados 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 você achar adequado, mas para neste tutorial em particular, esses métodos não serão usados e serão omitidos o exemplo de código nesta página para agilizar.

/** 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 ao 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 controladora facilita a comunicação entre o cliente e o repositório de dados. 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 que seja intrinsecamente com suporte no Spring Boot. Esse banco de dados também é usado em para armazenar outros materiais relacionados ao Google Sala de Aula informações imprecisas ou inadequadas. A configuração do banco de dados H2 requer a adição do seguinte para 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 dentro dele. Adicione o caminho ao banco de dados H2 para o .gitignore. Atualize spring.datasource.username e spring.datasource.password antes de executar o aplicativo para definir a banco de dados por um nome de usuário e senha de sua escolha. Para atualizar o nome de usuário e senha do banco de dados depois de executar o aplicativo, excluir o diretório h2 gerado, atualizar a configuração e executar o aplicativo novamente.

Definir a configuração spring.jpa.hibernate.ddl-auto como update garante que os dados armazenados no banco de dados são preservados quando o aplicativo é reiniciado. Para limpar o banco de dados toda vez que o aplicativo for reiniciado, defina este configuração como create.

Defina a configuração spring.jpa.open-in-view como false. Esta configuração está ativada e pode resultar em problemas de desempenho que são difíceis de diagnosticar na produção.

Conforme descrito anteriormente, você deve ser capaz de recuperar as credenciais de um usuário recorrente. Isso é facilitado pelo armazenamento de credenciais integrado suporte oferecido pela GoogleAuthorizationCodeFlow.

Na classe AuthService.java, defina um caminho para o arquivo em que o classe de credencial for armazenada. Neste exemplo, o arquivo é criado /credentialStore. Adicione o caminho para o armazenamento de credenciais ao arquivo .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 cria e retorna um objeto FileDataStoreFactory. Esse é o repositório de dados 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() no AuthService.java para incluir setDataStoreFactory neste local: GoogleAuthorizationCodeFlow Builder() e chamar 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 obtinha credenciais sem armazená-las qualquer lugar. Atualizar o método para armazenar as credenciais no repositório de dados indexado pelo ID do usuário.

O ID do usuário pode ser encontrado no objeto TokenResponse usando o id_token, mas precisa ser verificada primeiro. Caso contrário, o cliente aplicativos podem falsificar a identidade de usuários enviando mensagens de ao servidor. é recomendável usar o cliente da API do Google bibliotecas para validar o id_token. Consulte a [página de identidade do Google verificação do 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 a id_token for verificada, acesse 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 o 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. para 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ê vai receber um id na Parâmetro de consulta login_hint, que pode ser usado para recuperar um usuário específico registro.

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 recuperar um usuário no 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á tiver sido no banco de dados e atualizar o registro atual com novos valores. Caso contrário, criar um novo registro User e adicioná-lo ao banco de dados.

Python

Primeiro defina um método utilitário que implemente o armazenamento ou a atualização do seu modelo.

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 nas quais você pode salvar credenciais em seu banco de dados: quando o usuário retorna ao aplicativo no final do fluxo de autorização e ao emitir uma chamada de API. É aqui que já definiu a chave credentials da sessão.

Chame save_user_credentials no final do seu trajeto callback. Mantenha o user_info em vez de apenas extrair 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)

Atualize também as credenciais após as chamadas para a API. Neste caso, você pode fornecer as credenciais atualizadas como argumentos para o 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 H2 no seu banco de dados.

/** 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 nas quais você pode salvar credenciais em seu banco de dados: quando o usuário retorna ao aplicativo no final do fluxo de autorização e ao emitir uma chamada de API. É aqui que já definiu a chave credentials da sessão.

Chame saveUser no final da rota /callback. Mantenha os 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);

Atualize também as credenciais após as chamadas para a API. Neste caso, é possível fornecer as credenciais atualizadas como argumentos ao saveUser .

/** Save credentials in case access token was refreshed. */
saveUser(credentials, null, session);

Credenciais expiradas

Há alguns motivos pelos quais os tokens de atualização podem se tornar inválidos. Veja alguns exemplos:

  • O token de atualização não é usado há seis meses.
  • O usuário revoga as permissões de acesso do seu app.
  • O usuário muda as senhas.
  • O usuário pertence a uma organização do Google Cloud que tem controle de sessão políticas em vigor.

Adquira novos tokens enviando o usuário pelo fluxo de autorização novamente se as credenciais deles 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 esse for o caso, encaminhe-os para nossa página principal de complementos. Caso contrário, solicite fazer login.

Python

Verifique se o arquivo do banco de dados foi criado quando o aplicativo é lançado. Insira o código a seguir em um inicializador de módulo (como webapp/__init__.py no nosso exemplo) ou no método principal que inicia o servidor.

# Initialize the database file if not created.
if not os.path.exists(DATABASE_FILE_NAME):
    db.create_all()

Seu método deve processar o parâmetro de consulta login_hint como discutidos acima. Em seguida, carregue as credenciais da loja se este for um ciclo visitante. Você sabe que é um visitante recorrente se recebeu login_hint. Recupere todas as credenciais armazenadas para este usuário e carregue-as no 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, encaminhe o usuário para a página de login se não tivermos o credenciais. Se isso acontecer, encaminhe-os 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é sua rota de destino complementar (/addon-discovery no exemplo). Como discutido acima, é aqui que você processou o login_hint parâmetro de consulta.

Primeiro, verifique se a sessão tem credenciais. Se não tiverem, encaminhe usuário por meio do 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 o visitante for recorrente. Está 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 objeto credential repositório de dados configurado anteriormente e defina as credenciais na sessão. Se o credenciais não foram obtidas do repositório de dados 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 testes de professores usuários. Acesse a guia Atividades e crie uma Atividade. Clique em Clique no botão Complementos abaixo da área de texto e selecione o complemento. O iframe se abre, e o complemento carrega URI de configuração de anexo especificado no Página Configuração do app do SDK do Google Workspace Marketplace.

Parabéns! Você está pronto para prosseguir para a próxima etapa: como criar anexos e identificar a função do usuário.