מה עושים בכניסות חוזרות

זהו המדריך השלישי בסדרת המדריכים בנושא תוספים ל-Classroom.

במדריך הזה נסביר איך לטפל בביקורים חוזרים בתוסף שלנו על ידי אחזור אוטומטי של פרטי הכניסה שניתנו למשתמש בעבר. לאחר מכן אתם מפנים את המשתמשים לדפים שמהם הם יכולים לשלוח בקשות API באופן מיידי. זוהי התנהגות נדרשת של תוספים ל-Classroom.

במהלך ההסבר המפורט הזה תבצעו את הפעולות הבאות:

  • הטמעת אחסון קבוע של פרטי הכניסה של המשתמשים.
  • מאחזרים ומעריכים את פרמטר השאילתה של התוסף login_hint. זהו מספר מזהה ייחודי של Google של המשתמש המחובר.

אחרי שתסיימו, תוכלו לאשר למשתמשים גישה מלאה לאפליקציית האינטרנט שלכם ולבצע קריאות ל-Google APIs.

הסבר על פרמטרים של שאילתות iframe

כשפותחים את Classroom, נטען ה-URI של הגדרת הקובץ המצורף של התוסף. אפליקציית Classroom מוסיפה כמה פרמטרים של שאילתה GET ל-URI, שמכילים מידע הקשרי שימושי. לדוגמה, אם ה-URI של גילוי הקבצים המצורפים הוא https://example.com/addon,‏ Classroom יוצר את ה-iframe עם כתובת ה-URL של המקור שמוגדרת ל-https://example.com/addon?courseId=XXX&itemId=YYY&itemType=courseWork&addOnToken=ZZZ, כאשר XXX,‏ YYY ו-ZZZ הם מזהים של מחרוזות. במדריך בנושא iframe יש תיאור מפורט של התרחיש הזה.

יש חמישה פרמטרים אפשריים של שאילתה לכתובת ה-URL של מסמך Discovery:

  • courseId: המזהה של הקורס הנוכחי ב-Classroom.
  • itemId: המזהה של פריט הסטרימינג שהמשתמש עורך או יוצר.
  • itemType: סוג הפריט בשידור שהמשתמש יוצר או עורך, אחד מהערכים courseWork,‏ courseWorkMaterial או announcement.
  • addOnToken: אסימון שמשמש לאישור פעולות מסוימות בתוספים של Classroom.
  • login_hint: המזהה ב-Google של המשתמש הנוכחי.

ההסבר המפורט הזה מתייחס ל-login_hint. המשתמשים מנותבים בהתאם לשאלה אם הפרמטר הזה של השאילתה מסופק. אם הוא לא מסופק, הם מנותבים לתהליך ההרשאה, ואם הוא מסופק, הם מנותבים לדף הגילוי של התוסף.

גישה לפרמטרים של השאילתה

הפרמטרים של השאילתה מועברים לאפליקציית האינטרנט במחרוזת ה-URI. מאחסנים את הערכים האלה בסשן. הם משמשים בתהליך ההרשאה, וגם לאחסון ולאחזור של מידע על המשתמש. הפרמטרים האלה של השאילתה מועברים רק בפעם הראשונה שפותחים את התוסף.

Python

עוברים להגדרות של מסלולי Flask (routes.py אם פועלים לפי הדוגמה שסיפקנו). בחלק העליון של נתיב דף הנחיתה של התוסף (/classroom-addon בדוגמה שסיפקנו), מאחזרים ושומרים את פרמטר השאילתה 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")

מוודאים ש-login_hint (אם קיים) מאוחסן בסשן. זהו מקום מתאים לאחסון הערכים האלה. הם זמניים, ותקבלו ערכים חדשים כשהתוסף ייפתח.

# 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

עוברים למסלול הנחיתה של התוסף בכיתת הבקרה (/addon-discovery ב-AuthController.java בדוגמה שסופקה). בתחילת המסלול הזה, מאחזרים ושומרים את פרמטר השאילתה login_hint.

/** Retrieve the login_hint query parameter from the request URL if present. */
String login_hint = request.getParameter("login_hint");

מוודאים ש-login_hint (אם קיים) מאוחסן בסשן. זהו מקום מתאים לאחסון הערכים האלה. הם זמניים, ותקבלו ערכים חדשים כשהתוסף ייפתח.

/** 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);
}

הוספת פרמטרים של שאילתה לתהליך ההרשאה

צריך להעביר את הפרמטר login_hint גם לשרתי האימות של Google. ההנחיה הזו עוזרת בתהליך האימות. אם האפליקציה יודעת איזה משתמש מנסה לבצע אימות, השרת משתמש בהנחיה כדי לפשט את תהליך הכניסה על ידי מילוי מראש של שדה האימייל בטופס הכניסה.

Python

מנווטים לנתיב ההרשאה בקובץ השרת של Flask (/authorize בדוגמה שסיפקנו). מוסיפים את הארגומנט login_hint לקריאה ל-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

עוברים לשיטה authorize() בכיתה AuthService.java. מוסיפים את login_hint כפרמטר לשיטה, ומוסיפים את login_hint ואת הארגומנט לכלי ליצירת כתובות URL להרשאה.

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

הוספת אחסון קבוע לפרטי הכניסה של המשתמשים

אם מקבלים את הערך login_hint כפרמטר של שאילתה כשהתוסף נטען, זה מצביע על כך שהמשתמש כבר השלים את תהליך ההרשאה עבור האפליקציה שלנו. כדאי לאחזר את פרטי הכניסה הקודמים שלהם במקום לחייב אותם להיכנס שוב.

זוכרים שקיבלתם refresh token בסיום תהליך ההרשאה? שומרים את האסימון הזה. אפשר להשתמש בו שוב כדי לקבל אסימון גישה, שהוא לטווח קצר ונדרש לשימוש ב-Google APIs. שמרתם את פרטי הכניסה האלה קודם לכן בסשן, אבל אתם צריכים לשמור את פרטי הכניסה כדי לטפל בביקורים חוזרים.

הגדרת סכימת המשתמשים והגדרת מסד הנתונים

מגדירים סכמת מסד נתונים ל-User.

Python

הגדרת סכימת המשתמש

User מכיל את המאפיינים הבאים:

  • id: מזהה Google של המשתמש. הערך הזה צריך להיות זהה לערכים שמופיעים בפרמטר השאילתה login_hint.
  • display_name: השם הפרטי ושם המשפחה של המשתמש, למשל Alex Smith.
  • email: כתובת האימייל של המשתמש.
  • portrait_url: כתובת ה-URL של תמונת הפרופיל של המשתמש.
  • refresh_token: טוקן הרענון שהתקבל קודם.

בדוגמה הזו מיושם אחסון באמצעות SQLite, שנתמך באופן מקורי על ידי Python. הוא משתמש במודול flask_sqlalchemy כדי לנהל את מסד הנתונים שלנו.

הגדרת מסד הנתונים

קודם כל, מציינים מיקום קובץ למסד הנתונים שלנו. עוברים לקובץ ההגדרות של השרת (config.py בדוגמה שסיפקנו) ומוסיפים את הפרטים הבאים.

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

הפקודה הזו מפנה את Flask לקובץ data.sqlite באותה ספרייה שבה נמצא קובץ main.py.

לאחר מכן, עוברים לספריית המודולים ויוצרים קובץ models.py חדש. זהו webapp/models.py אם אתם פועלים לפי הדוגמה שסיפקנו. מוסיפים את השורות הבאות לקובץ החדש כדי להגדיר את הטבלה User. אם שם המודול שונה, מחליפים את webapp בשם המודול.

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())

לבסוף, בקובץ __init__.py של המודול, מוסיפים את השורות הבאות כדי לייבא את המודלים החדשים וליצור את מסד הנתונים.

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

הגדרת סכימת המשתמש

User מכיל את המאפיינים הבאים:

  • id: מזהה Google של המשתמש. הערך הזה צריך להיות זהה לערך שצוין בפרמטר השאילתה login_hint.
  • email: כתובת האימייל של המשתמש.

יוצרים קובץ schema.sql בספרייה resources של המודול. ‫Spring קורא את הקובץ הזה ויוצר סכימה למסד הנתונים בהתאם. מגדירים את הטבלה עם שם הטבלה, users ועמודות שמייצגות את המאפיינים User, id ו-email.

CREATE TABLE IF NOT EXISTS users (
    id VARCHAR(255) PRIMARY KEY, -- user's unique Google ID
    email VARCHAR(255), -- user's email address
);

יוצרים מחלקה ב-Java כדי להגדיר את המודל User למסד הנתונים. בדוגמה שסיפקת, זהו הערך User.java.

מוסיפים את ההערה @Entity כדי לציין שזהו POJO שאפשר לשמור במסד הנתונים. מוסיפים את ההערה @Table עם שם הטבלה המתאים שהגדרתם ב-schema.sql.

שימו לב שדוגמת הקוד כוללת בנאים ושיטות setter לשני המאפיינים. הקונסטרוקטור וה-setters משמשים ב-AuthController.java כדי ליצור או לעדכן משתמש במסד הנתונים. אפשר גם לכלול פונקציות getter ופונקציית toString, אבל במדריך הזה לא נעשה שימוש בפונקציות האלה, והן לא מופיעות בדוגמת הקוד שבדף הזה כדי לשמור על תמציתיות.

/** 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; }
}

ליצור ממשק בשם UserRepository.java לטיפול בפעולות CRUD במסד הנתונים. הממשק הזה הוא הרחבה של הממשק CrudRepository.

/** Provides CRUD operations for the User class by extending the
 *   CrudRepository interface. */
@Repository
public interface UserRepository extends CrudRepository<User, String> {
}

מחלקת הבקרה מאפשרת תקשורת בין הלקוח למאגר. לכן, צריך לעדכן את ה-constructor של מחלקת הבקרה כדי להוסיף את המחלקה 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;
}

הגדרת מסד הנתונים

כדי לאחסן מידע שקשור למשתמש, צריך להשתמש במסד נתונים H2 שנתמך באופן מובנה ב-Spring Boot. מסד הנתונים הזה משמש גם במדריכים הבאים לאחסון מידע נוסף שקשור ל-Classroom. כדי להגדיר את מסד הנתונים H2, צריך להוסיף את ההגדרה הבאה ל-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

ההגדרה spring.datasource.url יוצרת ספרייה בשם h2, עם הקובץ userdb שמאוחסן בתוכה. מוסיפים את הנתיב למסד הנתונים H2 אל .gitignore. לפני שמריצים את האפליקציה, צריך לעדכן את spring.datasource.username ואת spring.datasource.password כדי להגדיר את מסד הנתונים עם שם משתמש וסיסמה לפי בחירה. כדי לעדכן את שם המשתמש והסיסמה של מסד הנתונים אחרי הפעלת האפליקציה, צריך למחוק את התיקייה h2 שנוצרה, לעדכן את ההגדרות ולהפעיל מחדש את האפליקציה.

הגדרת התצורה של spring.jpa.hibernate.ddl-auto ל-update מבטיחה שהנתונים שמאוחסנים במסד הנתונים יישמרו כשמפעילים מחדש את האפליקציה. כדי לנקות את מסד הנתונים בכל הפעלה מחדש של האפליקציה, מגדירים את ההגדרה הזו לערך create.

מגדירים את התצורה spring.jpa.open-in-view לערך false. ההגדרה הזו מופעלת כברירת מחדל, וידוע שהיא עלולה לגרום לבעיות בביצועים שקשה לאבחן בסביבת ייצור.

כמו שצוין קודם, אתם צריכים להיות מסוגלים לאחזר את פרטי הכניסה של משתמש חוזר. התמיכה הזו מתאפשרת בזכות חנות האישורים המובנית שמוצעת על ידי GoogleAuthorizationCodeFlow.

במחלקת AuthService.java, מגדירים נתיב לקובץ שבו מאוחסנת מחלקת פרטי הכניסה. בדוגמה הזו, הקובץ נוצר בספרייה /credentialStore. מוסיפים את הנתיב למאגר האישורים לתג .gitignore. הספרייה הזו נוצרת ברגע שהמשתמש מתחיל את תהליך ההרשאה.

private static final File dataDirectory = new File("credentialStore");

לאחר מכן, יוצרים שיטה בקובץ AuthService.java שיוצרת אובייקט FileDataStoreFactory ומחזירה אותו. זהו מאגר הנתונים שבו מאוחסנים פרטי הכניסה.

/** 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;
}

מעדכנים את השיטה getFlow() ב-AuthService.java כך שתכלול את setDataStoreFactory בשיטה GoogleAuthorizationCodeFlow Builder() וקוראים ל-getCredentialDataStore() כדי להגדיר את מאגר הנתונים.

GoogleAuthorizationCodeFlow authorizationCodeFlow =
    new GoogleAuthorizationCodeFlow.Builder(
        HTTP_TRANSPORT,
        JSON_FACTORY,
        getClientSecrets(),
        getScopes())
    .setAccessType("offline")
    .setDataStoreFactory(getCredentialDataStore())
    .build();

לאחר מכן מעדכנים את getAndSaveCredentials(String authorizationCode). בעבר, השיטה הזו השיגה פרטי כניסה בלי לאחסן אותם בשום מקום. מעדכנים את השיטה לשמירת פרטי הכניסה במאגר הנתונים עם אינדקס לפי מזהה המשתמש.

אפשר לקבל את מזהה המשתמש מהאובייקט TokenResponse באמצעות id_token, אבל חובה לאמת אותו קודם. אחרת, אפליקציות לקוח יוכלו להתחזות למשתמשים על ידי שליחת מזהי משתמשים ששונו לשרת. מומלץ להשתמש בספריות הלקוח של Google API כדי לאמת את id_token. מידע נוסף זמין במאמר [Google Identity page on verifying the Google ID token].

// 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.");
}

אחרי שמאמתים את id_token, מקבלים את userId כדי לאחסן אותו יחד עם פרטי הכניסה שהתקבלו.

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

רוצה להתקשר שוב למספר flow.createAndStoreCredential ולהוסיף את userId?

// Save the user id and credentials to the configured FileDataStoreFactory.
Credential credential = flow.createAndStoreCredential(tokenResponse, userId);

מוסיפים שיטה למחלקה AuthService.java שמחזירה את פרטי הכניסה של משתמש ספציפי אם הוא קיים במאגר הנתונים.

/** 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;
    }
}

אחזור פרטי כניסה

מגדירים שיטה לאחזור Users. הפרמטר id של השאילתה login_hint מאפשר לאחזר רשומה של משתמש ספציפי.

Python

def get_credentials_from_storage(id):
    """
    Retrieves credentials from the storage and returns them as a dictionary.
    """
    return User.query.get(id)

Java

במחלקה AuthController.java, מגדירים שיטה לאחזור משתמש ממסד הנתונים על סמך מזהה המשתמש שלו.

/** 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;
}

אחסון פרטי כניסה

יש שני תרחישים לאחסון פרטי כניסה. אם ה-id של המשתמש כבר נמצא במסד הנתונים, המערכת תעדכן את הרשומה הקיימת בכל הערכים החדשים. אחרת, יוצרים רשומת User חדשה ומוסיפים אותה למסד הנתונים.

Python

קודם מגדירים שיטת עזר שמטמיעה את התנהגות האחסון או העדכון.

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()

יש שני מקרים שבהם כדאי לשמור את פרטי הכניסה במסד הנתונים: כשהמשתמש חוזר לאפליקציה בסוף תהליך ההרשאה, וכשמבצעים קריאה ל-API. כאן הגדרנו בעבר את מפתח credentials של הסשן.

תתקשר אל save_user_credentials בסוף המסלול callback. שומרים את האובייקט user_info במקום לחלץ רק את שם המשתמש.

# 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)

כדאי גם לעדכן את פרטי הכניסה אחרי כל קריאה ל-API. במקרה כזה, אפשר לספק את פרטי הכניסה המעודכנים כארגומנטים לפונקציה save_user_credentials.

# Save credentials in case access token was refreshed.
flask.session["credentials"] = credentials_to_dict(credentials)
save_user_credentials(credentials)

Java

קודם כול, מגדירים שיטה שמאחסנת או מעדכנת אובייקט User במסד הנתונים 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);
    }
}

יש שני מקרים שבהם כדאי לשמור את פרטי הכניסה במסד הנתונים: כשהמשתמש חוזר לאפליקציה בסוף תהליך ההרשאה, וכשמבצעים קריאה ל-API. כאן הגדרנו בעבר את מפתח credentials של הסשן.

מתקשרים אל saveUser בסוף המסלול /callback. צריך לשמור את האובייקט user_info במקום רק לחלץ את כתובת האימייל של המשתמש.

/** 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);

כדאי גם לעדכן את פרטי הכניסה אחרי כל קריאה ל-API. במקרה כזה, אפשר לספק את פרטי הכניסה המעודכנים כארגומנטים לשיטה saveUser.

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

פרטי כניסה שפג תוקפם

חשוב לדעת שיש כמה סיבות לכך שטוקנים לרענון יכולים להיות לא תקפים. למשל:

  • לא נעשה שימוש באסימון הרענון במשך שישה חודשים.
  • המשתמש מבטל את הרשאות הגישה של האפליקציה.
  • המשתמש משנה את הסיסמאות.
  • המשתמש שייך לארגון ב-Google Cloud שמוחלות בו מדיניות של בקרת סשנים.

אם פרטי הכניסה של המשתמשים יפסיקו להיות תקפים, צריך לשלוח אותם שוב לתהליך ההרשאה כדי לקבל טוקנים חדשים.

ניתוב אוטומטי של המשתמש

משנים את נתיב הנחיתה של התוסף כדי לזהות אם המשתמש אישר בעבר את האפליקציה שלנו. אם כן, צריך להפנות אותם לדף הראשי של התוסף. אחרת, מבקשים מהם להיכנס לחשבון.

Python

מוודאים שקובץ מסד הנתונים נוצר כשהאפליקציה מופעלת. מזינים את הקוד הבא ב-initializer של מודול (כמו webapp/__init__.py בדוגמה שסיפקנו) או בשיטה הראשית שמפעילה את השרת.

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

לאחר מכן, השיטה צריכה לטפל בפרמטר השאילתה login_hint כפי שהוסבר למעלה. אחר כך, טוענים את פרטי הכניסה לחנות אם מדובר במבקר חוזר. אם קיבלתם את הערך login_hint, סימן שהמשתמש ביקר באתר בעבר. מאחזרים את כל פרטי הכניסה ששמורים למשתמש הזה וטוענים אותם לתוך הסשן.

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

לבסוף, אם אין לנו את פרטי הכניסה של המשתמש, ננתב אותו לדף הכניסה. אם כן, ננתב אותם לדף הראשי של התוסף.

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

עוברים למסלול של דף הנחיתה של התוסף (/addon-discovery בדוגמה שמופיעה למעלה). כפי שצוין למעלה, כאן טיפלתם בפרמטר של השאילתה login_hint query.

קודם כל, בודקים אם יש פרטי כניסה בסשן. אם לא, צריך לנתב את המשתמש דרך תהליך האימות על ידי קריאה לשיטה 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);
}

לאחר מכן, טוענים את המשתמש ממסד הנתונים של H2 אם מדובר במבקר חוזר. אם מקבלים את פרמטר השאילתה login_hint, מדובר במבקר חוזר. אם המשתמש קיים במסד הנתונים H2, טוענים את פרטי הכניסה ממאגר פרטי הכניסה שהוגדר קודם ומגדירים את פרטי הכניסה בסשן. אם פרטי הכניסה לא התקבלו ממאגר פרטי הכניסה, צריך להפנות את המשתמש לתהליך האימות על ידי קריאה ל-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);
    }
}

לבסוף, מעבירים את המשתמש לדף הנחיתה של התוסף.

/** Finally, if there are credentials in the session and in persistent
 *   storage, direct the user to the addon-discovery page. */
return "addon-discovery";

בדיקת התוסף

נכנסים ל-Google Classroom כאחד ממשתמשי הבדיקה מסוג מורה. עוברים לכרטיסייה סביבת הלמידה ויוצרים מטלה חדשה. לוחצים על הלחצן תוספים שמתחת לאזור הטקסט, ואז בוחרים את התוסף הרצוי. ה-iframe נפתח והתוסף טוען את ה-URI של הגדרת הקובץ המצורף שציינתם בדף App Configuration ב-SDK של Google Workspace Marketplace.

מעולה! אפשר לעבור לשלב הבא: יצירת קבצים מצורפים וזיהוי התפקיד של המשתמש.