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

זוהי ההדרכה המפורטת השלישית בסדרת ההדרכה המפורטת על תוספים ל-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 הם מזהי מחרוזות. לתיאור מפורט של התרחיש הזה תוכלו להיעזר במדריך בנושא iframes.

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

  • courseId: המזהה של הקורס הנוכחי ב-Classroom.
  • itemId: המזהה של הפריט במקור הנתונים שהמשתמש עורך או יוצר.
  • itemType: סוג הפריט ב-stream שהמשתמש יוצר או עורך, אחד מהערכים 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 or hd query parameters from the request URL. */
String login_hint = request.getParameter("login_hint");
String hd = request.getParameter("hd");

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

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

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

צריך להעביר את הפרמטר 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

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

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

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

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

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

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

צריך להגדיר סכימה של מסד נתונים ל-User.

Python

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

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

  • id: מזהה Google של המשתמש. הערך הזה צריך להתאים לערכים שצוינו בפרמטר השאילתה login_hint.
  • display_name: השם הפרטי ושם המשפחה של המשתמש, למשל "אלכס כהן".
  • 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.

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

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

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

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

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

// 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. הפרמטר של השאילתה login_hint מכיל את הערך id, ובעזרתו אפשר לאחזר רשומה ספציפית של משתמש.

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. במקרה כזה, תוכלו לספק את פרטי הכניסה המעודכנים כארגומנטים ל-method 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. במקרה הזה, אפשר לספק את פרטי הכניסה המעודכנים כארגומנטים ל-method saveUser.

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

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

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

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

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

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

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

Python

חשוב לוודא שקובץ מסד הנתונים נוצר כשהאפליקציה מופעלת. מוסיפים את הקוד הבא למאתחל של מודול (למשל 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.

קודם כול בודקים אם יש פרטי כניסה בסשן. אם הם לא מאפשרים זאת, מנתבים את המשתמש דרך תהליך האימות על ידי קריאה ל-method 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 להגדרת קובץ מצורף שציינתם בדף הגדרת האפליקציה ב-Google Workspace Marketplace SDK.

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