התחברות לשירותים שאינם של Google מתוסף Google Workspace

פרויקט התוסף של Google Workspace יכול להתחבר ישירות למוצרים רבים של Google באמצעות השירותים המובְנים והמתקדמים של Apps Script.

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

עם זאת, אם לשירות שאינו של Google נדרשת הרשאה, עליכם להגדיר OAuth עבור השירות הזה. תוכלו לפשט את התהליך באמצעות ספריית OAuth2 ל-Apps Script (יש גם גרסה של OAuth1).

שימוש בשירות OAuth

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

תהליך ההרשאה כולל:

  1. הצגת התראה למשתמש שנדרשת הרשאה, ושליחת קישור כדי להתחיל בתהליך.
  2. קבלת הרשאה מהשירות שאינו של Google.
  3. צריך לרענן את התוסף כדי לנסות שוב לגשת למשאב המוגן.

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

זיהוי שנדרשת הרשאה

יכולות להיות סיבות שונות לכך שאין לבקשה הרשאה לגשת למשאב מוגן, למשל:

  • אסימון הגישה עוד לא נוצר או שתוקפו פג.
  • אסימון הגישה לא מכסה את המשאב המבוקש.
  • אסימון הגישה לא מכסה את ההיקפים הנדרשים של הבקשה.

קוד התוסף אמור לזהות את המקרים האלה. הפונקציה hasAccess() של ספריית OAuth יכולה להודיע אם יש לכם כרגע גישה לשירות. לחלופין, כשמשתמשים בבקשות UrlFetchApp fetch(), אפשר להגדיר את הפרמטר muteHttpExceptions לערך true. כך הבקשה לא מפעילה חריגה כשהבקשה נכשלה, ואפשר לבדוק את קוד התגובה של הבקשה ואת התוכן באובייקט HttpResponse שמוחזר.

כשהתוסף מזהה שנדרשת הרשאה, הוא אמור להפעיל את תהליך ההרשאה.

הפעלה של תהליך ההרשאה

הפעלת תהליך ההרשאה מתבצעת על ידי שימוש בשירות הכרטיסים כדי ליצור אובייקט AuthorizationException, להגדיר את המאפיינים שלו ולבצע קריאה לפונקציה throwException(). לפני מחילים את החריגה, צריך לספק את הפרטים הבאים:

  1. חובה. כתובת URL של הרשאה. המיקום הזה נקבע על ידי השירות שאינו של Google, והוא המיקום שאליו המשתמש מועבר בתחילת תהליך ההרשאה. הגדרתם את כתובת ה-URL הזו באמצעות הפונקציה setAuthorizationUrl().
  2. חובה. מחרוזת של השם המוצג של המשאב. מזהה את המשאב למשתמש כשמתבקשת הרשאה. השם הזה מוגדר באמצעות הפונקציה setResourceDisplayName().
  3. השם של פונקציית הקריאה החוזרת שיוצרת בקשת הרשאה בהתאמה אישית. הקריאה החוזרת (callback) מחזירה מערך של אובייקטים מובנים של Card שמרכיבים ממשק משתמש לצורך טיפול בהרשאה. זה אופציונלי. אם לא מגדירים אותו, המערכת תשתמש בכרטיס ההרשאה שמוגדר כברירת מחדל. מגדירים את פונקציית הקריאה החוזרת באמצעות הפונקציה setCustomUiCallback().

דוגמה להגדרה של OAuth שאינו של Google

דוגמת הקוד הזו מדגימה איך להגדיר תוסף לשימוש בממשק שאינו Google API שנדרש לו פרוטוקול OAuth. נעשה בו שימוש ב-OAuth2 ל-Apps Script כדי ליצור שירות לגישה ל-API.

/**
 * Attempts to access a non-Google API using a constructed service
 * object.
 *
 * If your add-on needs access to non-Google APIs that require OAuth,
 * you need to implement this method. You can use the OAuth1 and
 * OAuth2 Apps Script libraries to help implement it.
 *
 * @param {String} url         The URL to access.
 * @param {String} method_opt  The HTTP method. Defaults to GET.
 * @param {Object} headers_opt The HTTP headers. Defaults to an empty
 *                             object. The Authorization field is added
 *                             to the headers in this method.
 * @return {HttpResponse} the result from the UrlFetchApp.fetch() call.
 */
function accessProtectedResource(url, method_opt, headers_opt) {
  var service = getOAuthService();
  var maybeAuthorized = service.hasAccess();
  if (maybeAuthorized) {
    // A token is present, but it may be expired or invalid. Make a
    // request and check the response code to be sure.

    // Make the UrlFetch request and return the result.
    var accessToken = service.getAccessToken();
    var method = method_opt || 'get';
    var headers = headers_opt || {};
    headers['Authorization'] =
        Utilities.formatString('Bearer %s', accessToken);
    var resp = UrlFetchApp.fetch(url, {
      'headers': headers,
      'method' : method,
      'muteHttpExceptions': true, // Prevents thrown HTTP exceptions.
    });

    var code = resp.getResponseCode();
    if (code >= 200 && code < 300) {
      return resp.getContentText("utf-8"); // Success
    } else if (code == 401 || code == 403) {
       // Not fully authorized for this action.
       maybeAuthorized = false;
    } else {
       // Handle other response codes by logging them and throwing an
       // exception.
       console.error("Backend server error (%s): %s", code.toString(),
                     resp.getContentText("utf-8"));
       throw ("Backend server error: " + code);
    }
  }

  if (!maybeAuthorized) {
    // Invoke the authorization flow using the default authorization
    // prompt card.
    CardService.newAuthorizationException()
        .setAuthorizationUrl(service.getAuthorizationUrl())
        .setResourceDisplayName("Display name to show to the user")
        .throwException();
  }
}

/**
 * Create a new OAuth service to facilitate accessing an API.
 * This example assumes there is a single service that the add-on needs to
 * access. Its name is used when persisting the authorized token, so ensure
 * it is unique within the scope of the property store. You must set the
 * client secret and client ID, which are obtained when registering your
 * add-on with the API.
 *
 * See the Apps Script OAuth2 Library documentation for more
 * information:
 *   https://github.com/googlesamples/apps-script-oauth2#1-create-the-oauth2-service
 *
 *  @return A configured OAuth2 service object.
 */
function getOAuthService() {
  return OAuth2.createService('SERVICE_NAME')
      .setAuthorizationBaseUrl('SERVICE_AUTH_URL')
      .setTokenUrl('SERVICE_AUTH_TOKEN_URL')
      .setClientId('CLIENT_ID')
      .setClientSecret('CLIENT_SECRET')
      .setScope('SERVICE_SCOPE_REQUESTS')
      .setCallbackFunction('authCallback')
      .setCache(CacheService.getUserCache())
      .setPropertyStore(PropertiesService.getUserProperties());
}

/**
 * Boilerplate code to determine if a request is authorized and returns
 * a corresponding HTML message. When the user completes the OAuth2 flow
 * on the service provider's website, this function is invoked from the
 * service. In order for authorization to succeed you must make sure that
 * the service knows how to call this function by setting the correct
 * redirect URL.
 *
 * The redirect URL to enter is:
 * https://script.google.com/macros/d/<Apps Script ID>/usercallback
 *
 * See the Apps Script OAuth2 Library documentation for more
 * information:
 *   https://github.com/googlesamples/apps-script-oauth2#1-create-the-oauth2-service
 *
 *  @param {Object} callbackRequest The request data received from the
 *                  callback function. Pass it to the service's
 *                  handleCallback() method to complete the
 *                  authorization process.
 *  @return {HtmlOutput} a success or denied HTML message to display to
 *          the user. Also sets a timer to close the window
 *          automatically.
 */
function authCallback(callbackRequest) {
  var authorized = getOAuthService().handleCallback(callbackRequest);
  if (authorized) {
    return HtmlService.createHtmlOutput(
      'Success! <script>setTimeout(function() { top.window.close() }, 1);</script>');
  } else {
    return HtmlService.createHtmlOutput('Denied');
  }
}

/**
 * Unauthorizes the non-Google service. This is useful for OAuth
 * development/testing.  Run this method (Run > resetOAuth in the script
 * editor) to reset OAuth to re-prompt the user for OAuth.
 */
function resetOAuth() {
  getOAuthService().reset();
}

יצירת בקשה להרשאה מותאמת אישית

כרטיס הרשאה לשירות שאינו של Google

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

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

הכרטיס שמוחזר חייב לבצע את הפעולות הבאות:

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

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

הדוגמה הבאה מראה פונקציית קריאה חוזרת של בקשת הרשאה מותאמת אישית:

/**
 * Returns an array of cards that comprise the customized authorization
 * prompt. Includes a button that opens the proper authorization link
 * for a non-Google service.
 *
 * When creating the text button, using the
 * setOnClose(CardService.OnClose.RELOAD_ADD_ON) function forces the add-on
 * to refresh once the authorization flow completes.
 *
 * @return {Card[]} The card representing the custom authorization prompt.
 */
function create3PAuthorizationUi() {
  var service = getOAuthService();
  var authUrl = service.getAuthorizationUrl();
  var authButton = CardService.newTextButton()
      .setText('Begin Authorization')
      .setAuthorizationAction(CardService.newAuthorizationAction()
          .setAuthorizationUrl(authUrl));

  var promptText =
      'To show you information from your 3P account that is relevant' +
      ' to the recipients of the email, this add-on needs authorization' +
      ' to: <ul><li>Read recipients of the email</li>' +
      '         <li>Read contact information from 3P account</li></ul>.';

  var card = CardService.newCardBuilder()
      .setHeader(CardService.newCardHeader()
          .setTitle('Authorization Required'))
      .addSection(CardService.newCardSection()
          .setHeader('This add-on needs access to your 3P account.')
          .addWidget(CardService.newTextParagraph()
              .setText(promptText))
          .addWidget(CardService.newButtonSet()
              .addButton(authButton)))
      .build();
  return [card];
}

/**
 * When connecting to the non-Google service, pass the name of the
 * custom UI callback function to the AuthorizationException object
 */
function accessProtectedResource(url, method_opt, headers_opt) {
  var service = getOAuthService();
  if (service.hasAccess()) {
    // Make the UrlFetch request and return the result.
    // ...
  } else {
    // Invoke the authorization flow using a custom authorization
    // prompt card.
    CardService.newAuthorizationException()
        .setAuthorizationUrl(service.getAuthorizationUrl())
        .setResourceDisplayName("Display name to show to the user")
        .setCustomUiCallback('create3PAuthorizationUi')
        .throwException();
  }
}

ניהול התחברות של צד שלישי באפליקציות של Google Workspace

אחת מהאפליקציות הנפוצות של תוספים ל-Google Workspace היא לספק ממשק לאינטראקציה עם מערכת של צד שלישי מתוך אפליקציה מארחת ב-Google Workspace. בעזרת ספריית OAuth2 ל-Apps Script תוכלו ליצור ולנהל חיבורים לשירותי צד שלישי.

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

מאפייני משתמש

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

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

אסימונים מזהים

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