זהו המדריך השלישי בסדרת המדריכים בנושא תוספים ל-Classroom.
במדריך הזה תלמדו איך לטפל בביקור חוזר ב-Add-on שלנו על ידי אחזור אוטומטי של פרטי הכניסה של המשתמש שהוקצו לו בעבר. לאחר מכן תוכלו לנתב את המשתמשים לדפים שמהם הם יוכלו לשלוח בקשות 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 של מסמך ה-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
עוברים ל-method 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
כפרמטר של שאילתה כשהתוסף נטען, סימן שהמשתמש כבר השלים את תהליך ההרשאה לאפליקציה שלנו. צריך לאחזר את פרטי הכניסה הקודמים שלהם במקום לאלץ אותם להיכנס שוב.
חשוב לזכור שקיבלת אסימון רענון בסיום תהליך ההרשאה. שומרים את האסימון הזה. הוא ישמש לקבלת אסימון גישה לטווח קצר, שנחוץ לשימוש ב-Google APIs. כבר שמרתם את פרטי הכניסה האלה בסשן, אבל אתם צריכים לשמור אותם כדי לטפל בביקורים חוזרים.
הגדרת הסכימה של User והגדרת מסד הנתונים
מגדירים סכמה של מסד נתונים ל-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
.
שימו לב שדוגמת הקוד כוללת קונסטרוקטורים ו-setters לשני המאפיינים. ה-constructor וה-setters משמשים ב-AuthController.java
כדי ליצור או לעדכן משתמש במסד הנתונים. אפשר גם לכלול פונקציות getter ו-method 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. במקרה כזה, אפשר לספק את פרטי הכניסה המעודכנים כארגומנטים ל-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 בתור אחד ממשתמשי הבדיקה בתפקיד מורה. עוברים לכרטיסייה עבודות ויוצרים מטלה חדשה. לוחצים על הלחצן Add-ons מתחת לאזור הטקסט ובוחרים את התוסף הרצוי. ה-iframe נפתח והתוסף טוען את URI להגדרת הקובץ המצורף שציינתם בדף הגדרת האפליקציה של ה-SDK של Google Workspace Marketplace.
מעולה! עכשיו אפשר להמשיך לשלב הבא: יצירת קבצים מצורפים וזיהוי התפקיד של המשתמש.