Processar logins repetidos

Este é o terceiro tutorial da série de complementos do Google Sala de Aula.

Neste tutorial, você lida com visitas repetidas ao nosso complemento recuperando automaticamente as credenciais concedidas anteriormente por um usuário. Em seguida, você encaminha os usuários para páginas de onde eles podem emitir imediatamente solicitações de API. Esse é um comportamento obrigatório para os complementos do Google Sala de Aula.

Neste tutorial, você vai fazer o seguinte:

  • Implementar armazenamento permanente para nossas credenciais de usuário.
  • Recupere e avalie os seguintes parâmetros de consulta de complementos:
    • login_hint: o número do ID do Google do usuário conectado.
    • hd: o domínio do usuário que fez login.

Observe que apenas um deles será enviado. A API Classroom enviará o parâmetro hd se o usuário AINDA NÃO tiver autorizado o app. Caso contrário, a API enviará login_hint. A lista completa de parâmetros de consulta está disponível na página de guia de iframes.

Quando terminar, 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 anexos do complemento quando ele é aberto. O Google Sala de Aula anexa 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&postId=YYY&addOnToken=ZZZ, em que XXX, YYY e ZZZ são IDs de string. Consulte o guia de iframes para ver 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.
  • postId: o ID da postagem da atividade que o usuário está editando ou criando.
  • 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.
  • hd: o domínio de host do usuário atual, como example.com.

Este tutorial aborda hd e login_hint. Os usuários são encaminhados com base no parâmetro de consulta fornecido, seja para o fluxo de autorização, se hd, ou para a página de descoberta de complementos, se login_hint.

Acessar os parâmetros de consulta

Conforme descrito acima, os parâmetros de consulta são passados para seu aplicativo da Web na string do 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 suas rotas do Flask (routes.py se estiver seguindo nosso exemplo). Na parte de cima da rota de destino do complemento (/classroom-addon no exemplo fornecido), recupere e armazene os parâmetros de consulta login_hint e hd:

# Retrieve the login_hint and hd query parameters.
login_hint = flask.request.args.get("login_hint")
hd = flask.request.args.get("hd")

Verifique se login_hint e hd estão armazenados na sessão. Esse é um local apropriado 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.

# If neither query parameter is available, use the values in the session.
if login_hint is None and hd is None:
    login_hint = flask.session.get("login_hint")
    hd = flask.session.get("hd")

# If there's no login_hint query parameter, then check for hd.
# Send the user to the sign in page.
elif hd is not None:
    flask.session["hd"] = hd
    return start_auth_flow()

# If the login_hint query parameter is available, we'll store it in the
# session.
else:
    flask.session["login_hint"] = login_hint

Java

Navegue até a rota de destino do complemento na classe de controle (/addon-discovery em AuthController.java no exemplo fornecido). No início desse trajeto, recupere e armazene os parâmetros de consulta login_hint e hd.

/** Retrieve the login_hint or hd query parameters from the request URL. */
String login_hint = request.getParameter("login_hint");
String hd = request.getParameter("hd");

Verifique se login_hint e hd estão armazenados na sessão. Esse é um local apropriado para armazenar esses valores. Eles são temporários e você recebe novos valores quando o complemento é aberto.

/** If neither query parameter is sent, use the values in the session. */
if (login_hint == null && hd == null) {
    login_hint = (String) session.getAttribute("login_hint");
    hd = (String) session.getAttribute("hd");
}

/** If the hd query parameter is provided, add hd to the session and route
*   the user to the authorization page. */
else if (hd != null) {
    session.setAttribute("hd", hd);
    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

Os parâmetros login_hint e hd também precisam ser transmitidos aos servidores de autenticação do Google. Isso facilita o processo de autenticação. Se o aplicativo souber qual usuário está tentando se autenticar, o servidor usará a dica para simplificar o fluxo de login preenchendo automaticamente 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 exemplo fornecido). Adicione os argumentos login_hint e hd à 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"),
    # If we don't have login_hint, passing hd will reduce the list of
    # accounts in the account chooser to only those with the same domain.
    hd=flask.session.get("hd"))

Java

Navegue até o método authorize() na classe AuthService.java. Adicione login_hint e hd como parâmetros ao método e os argumentos login_hint e hd ao criador de URLs de autorização.

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

Adicionar armazenamento permanente para credenciais de usuário

Receber login_hint como parâmetro de consulta quando o complemento é carregado é uma indicação de que o usuário já concluiu o fluxo de autorização do nosso aplicativo. Recupere as credenciais anteriores em vez de forçá-los a 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. Ele precisa ser reutilizado para receber um token de acesso, que é de curta duração e necessário para usar as APIs do Google. Você já 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

Configure 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 Google do usuário. Ela precisa corresponder aos valores fornecidos no parâmetro de consulta login_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 nosso gerenciamento do banco de dados.

Configurar o banco de dados

Primeiro, especifique um local de arquivo para nosso banco de dados. Navegue até o arquivo de configuração do servidor (config.py no exemplo fornecido) e adicione as informações a seguir.

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

Em seguida, navegue até o diretório do módulo e crie um novo arquivo models.py. Esse valor será webapp/models.py se você estiver seguindo nosso exemplo. Adicione o seguinte 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 código abaixo 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 Google do usuário. Ele precisa 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. O Spring lê esse arquivo e gera um esquema para o banco de dados adequadamente. 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 para o banco de dados. No exemplo fornecido, é User.java.

Adicione a anotação @Entity para indicar que esse é 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.

Observe que 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, mas para este tutorial específico, esses métodos não são usados e são omitidos do exemplo de código desta página para simplificar.

/** 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 do controlador 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 intrínseco ao Spring Boot. Esse banco de dados também será 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 ao 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 para o banco de dados H2 ao .gitignore. Atualize spring.datasource.username e 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 preferência. Para atualizar o nome de usuário e a senha do banco de dados depois de executar o 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 de spring.jpa.open-in-view como false. Essa configuração é ativada por padrão e pode resultar em problemas de desempenho difíceis de diagnosticar na produção.

Conforme descrito anteriormente, você precisa ser capaz de recuperar as credenciais de um usuário recorrente. Isso é facilitado pelo suporte integrado ao armazenamento de credenciais oferecido 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 o armazenamento 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 cria e retorna um objeto FileDataStoreFactory. Esse é o repositório de dados que armazena as 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 armazenamento 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). Anteriormente, esse método recebia credenciais sem armazená-las em lugar algum. Atualize 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 id_token, mas precisa ser verificado primeiro. Caso contrário, os aplicativos cliente podem se passar por usuários enviando IDs de usuário modificados para o servidor. É recomendável usar as bibliotecas de cliente das APIs do Google para validar o id_token. Consulte a [página do Google Identity 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 de verificar o id_token, consiga o userId para armazenar com as credenciais recebidas.

// Obtain the user id from the id_token.
Payload payload = idToken.getPayload();
String userId = payload.getSubject();

Atualize a chamada de 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 retorna as credenciais de um usuário específico, se ele existir no armazenamento 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 recuperar 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 existente com os novos valores. Caso contrário, crie um novo registro User e adicione-o ao banco de dados.

Python

Primeiro, defina um método utilitário que implemente o comportamento de armazenamento ou 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 é possível salvar credenciais no banco de dados: quando o usuário retorna ao aplicativo ao final do fluxo de autorização e ao emitir uma chamada de API. É aqui que definimos a chave credentials da sessão.

Chame save_user_credentials no final do trajeto callback. Mantenha o objeto 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)

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 é possível salvar credenciais no banco de dados: quando o usuário retorna ao aplicativo ao final do fluxo de autorização e ao emitir uma chamada de API. É aqui que definimos 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, forneça 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 os tokens de atualização se tornarem 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 app.
  • o usuário mudar as senhas;
  • O usuário pertence a uma organização do Google Cloud que tem políticas de controle de sessão em vigor.

adquirir novos tokens enviando o usuário pelo fluxo de autorização novamente se as credenciais se tornarem inválidas;

Encaminhar o usuário automaticamente

Modifique a rota de destino do complemento para detectar se o usuário autorizou nosso aplicativo anteriormente. Em caso afirmativo, encaminhe-os para nossa página principal de complementos. Caso contrário, peça que ele faça login.

Python

Certifique-se de que o arquivo do banco de dados tenha sido criado quando o aplicativo for iniciado. Insira o seguinte em um inicializador de módulo (como webapp/__init__.py no exemplo fornecido) 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 precisa processar os parâmetros de consulta login_hint e hd conforme discutido acima. Em seguida, carregue as credenciais da loja se for um visitante recorrente. Você sabe que é um visitante reincidente se recebeu login_hint. Recupere todas 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, encaminhe o usuário para a página de login se não tivermos as credenciais dele. Se o fizermos, 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é a rota de destino do complemento (/addon-discovery no exemplo fornecido). Conforme discutido acima, é aqui que você lidou com os parâmetros de consulta login_hint e hd.

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 for um visitante recorrente. Ele será um visitante repetido se você receber o parâmetro de consulta login_hint. Se o usuário existir no banco de dados H2, carregue as credenciais do repositório de credenciais configurados anteriormente e defina-as na sessão. Se as credenciais não foram recebidas 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, encaminhe 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 usuários de teste de Professor. Navegue até 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 anexos especificado na página Configuração do app do SDK do GWM.

Parabéns! Está tudo pronto para a próxima etapa: criar anexos e identificar a função do usuário.