การเชื่อมต่อกับบริการที่ไม่ใช่ของ 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 จะจัดการรายละเอียดเหล่านี้ ส่วนเสริมของคุณจะต้องตรวจพบเมื่อต้องมีการให้สิทธิ์เท่านั้น และเรียกใช้ขั้นตอนการให้สิทธิ์เมื่อจำเป็นเท่านั้น

การตรวจสอบว่าต้องมีการให้สิทธิ์นั้น

คำขออาจไม่มีสิทธิ์เข้าถึงทรัพยากรที่มีการป้องกันด้วยเหตุผลหลายประการ เช่น

  • ยังไม่ได้สร้างโทเค็นเพื่อการเข้าถึงหรือโทเค็นหมดอายุแล้ว
  • โทเค็นเพื่อการเข้าถึงไม่ครอบคลุมทรัพยากรที่ขอ
  • โทเค็นเพื่อการเข้าถึงไม่ครอบคลุมขอบเขตที่จำเป็นของคำขอ

โค้ดส่วนเสริมควรตรวจพบกรณีเหล่านี้ ฟังก์ชันไลบรารี OAuth hasAccess() จะบอกให้คุณทราบว่าขณะนี้คุณมีสิทธิ์เข้าถึงบริการหนึ่งๆ หรือไม่ หรือเมื่อใช้คำขอ UrlFetchApp fetch() คุณจะตั้งค่าพารามิเตอร์ muteHttpExceptions เป็น true ได้ ซึ่งจะป้องกันไม่ให้คําขอให้การยกเว้นเมื่อคําขอล้มเหลว และช่วยให้คุณตรวจสอบโค้ดตอบกลับของคําขอและเนื้อหาในออบเจ็กต์ HttpResponse ที่แสดงผลได้

เมื่อตรวจพบว่าจำเป็นต้องมีการให้สิทธิ์ ส่วนเสริมจะทริกเกอร์ขั้นตอนการให้สิทธิ์

การเรียกใช้ขั้นตอนการให้สิทธิ์

คุณเรียกใช้ขั้นตอนการให้สิทธิ์ได้โดยใช้บริการการ์ดเพื่อสร้างออบเจ็กต์ AuthorizationException ตั้งค่าพร็อพเพอร์ตี้ แล้วเรียกใช้ฟังก์ชัน throwException() ก่อนที่จะส่งข้อยกเว้น คุณต้องให้ข้อมูลต่อไปนี้

  1. ต้องระบุ URL การให้สิทธิ์ ข้อมูลนี้จะระบุโดยบริการที่ไม่ใช่ของ Google และเป็นตำแหน่งที่ระบบพาผู้ใช้ไปเมื่อขั้นตอนการให้สิทธิ์เริ่มต้น คุณจะตั้งค่า URL นี้โดยใช้ฟังก์ชัน setAuthorizationUrl()
  2. ต้องระบุ สตริงชื่อที่แสดงของทรัพยากร ระบุทรัพยากรให้กับผู้ใช้ เมื่อมีการขอสิทธิ์ คุณจะตั้งชื่อนี้ได้โดยใช้ฟังก์ชัน setResourceDisplayName()
  3. ชื่อของฟังก์ชันเรียกกลับที่สร้างพรอมต์การให้สิทธิ์ที่กำหนดเอง โค้ดเรียกกลับนี้จะส่งคืนอาร์เรย์ของออบเจ็กต์ Card ที่สร้างขึ้น ซึ่งเขียน UI สำหรับการจัดการการให้สิทธิ์ ขั้นตอนนี้เป็นขั้นตอนที่ไม่บังคับ หากไม่ตั้งค่า ระบบจะใช้บัตรการอนุมัติเริ่มต้น ให้ตั้งค่าฟังก์ชันเรียกกลับโดยใช้ฟังก์ชัน setCustomUiCallback()

ตัวอย่างการกำหนดค่า OAuth ที่ไม่ใช่ของ Google

ตัวอย่างโค้ดนี้แสดงวิธีกำหนดค่าส่วนเสริมเพื่อใช้ API ที่ไม่ใช่ของ Google ซึ่งต้องใช้ OAuth โดยใช้ OAuth2 for 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

โดยค่าเริ่มต้น ข้อความแจ้งการให้สิทธิ์จะไม่มีการแสดงแบรนด์ และจะใช้สตริงชื่อที่แสดงเพื่อระบุทรัพยากรที่ส่วนเสริมพยายามเข้าถึงเท่านั้น อย่างไรก็ตาม ส่วนเสริมสามารถกำหนดบัตรกันวงเงินแบบกำหนดเองเพื่อวัตถุประสงค์เดียวกันและอาจมีข้อมูลเพิ่มเติมและการสร้างแบรนด์ได้

คุณกำหนดข้อความแจ้งที่กำหนดเองได้โดยใช้ฟังก์ชันเรียกกลับของ UI ที่กำหนดเอง ซึ่งแสดงผลอาร์เรย์ของออบเจ็กต์ 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