الاتصال بخدمات غير تابعة لشركة Google من خلال إضافة Google Workspace

يمكن ربط مشروع "إضافة Google Workspace" مباشرةً بالعديد من منتجات Google باستخدام الخدمات المدمجة والمتقدّمة في "برمجة التطبيقات".

ويمكنك أيضًا الوصول إلى واجهات برمجة التطبيقات والخدمات غير التابعة لشركة Google. إذا لم تكن الخدمة تتطلّب الحصول على إذن، يمكنك عادةً تقديم طلب مناسب UrlFetch ثم إضافتك لتفسير الردّ.

ومع ذلك، إذا كانت الخدمة غير التابعة لـ Google تتطلب إذنًا، يجب ضبط OAuth لهذه الخدمة. يمكنك تسهيل هذه العملية باستخدام مكتبة OAuth2 لبرمجة التطبيقات (هناك أيضًا إصدار 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. اسم دالة معاودة الاتصال التي تنشئ طلب تفويض مخصصًا. تعرض معاودة الاتصال هذه مصفوفة من عناصر Card المنشأة التي تنشئ واجهة مستخدم لمعالجة التفويض. هذه خطوة اختيارية، وإذا لم يتم ضبط السياسة، يتم استخدام بطاقة التفويض التلقائية. يمكنك ضبط دالة معاودة الاتصال باستخدام الدالة setCustomUiCallback().

مثال على ضبط OAuth لا يتبع Google

يعرض نموذج الرمز هذا كيفية إعداد إضافة لاستخدام واجهة برمجة تطبيقات غير تابعة لـ Google تتطلب OAuth. يستخدم OAuth2 لبرمجة التطبيقات لإنشاء خدمة للوصول إلى واجهة برمجة التطبيقات.

/**
 * 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 لبرمجة التطبيقات في إنشاء وإدارة الاتصالات بخدمات الجهات الخارجية.

غالبًا ما تتطلب الأنظمة التابعة للجهات الخارجية أن يسجِّل المستخدم الدخول باستخدام رقم تعريف مستخدم أو كلمة مرور أو بيانات اعتماد أخرى. عندما يسجّل أحد المستخدمين الدخول إلى خدمتك التابعة لجهة خارجية أثناء استخدامه أحد مضيفي Google Workspace، عليك التأكّد من أنه لن يحتاج إلى تسجيل الدخول مرة أخرى عند التبديل إلى مضيف Google Workspace آخر. لمنع تكرار طلبات تسجيل الدخول، استخدِم خصائص المستخدمين أو الرموز المميزة للمعرّف. ويتم شرحها في الأقسام التالية.

خصائص المستخدمين

يمكنك تخزين بيانات تسجيل دخول مستخدم في خصائص المستخدمين ضمن "برمجة التطبيقات". على سبيل المثال، يمكنك إنشاء رمز JWT الخاص بك من خدمة تسجيل الدخول وتسجيل ذلك في خاصيّة المستخدم، أو تسجيل اسم المستخدم وكلمة المرور للخدمة الخاصة به.

يتم تحديد خصائص المستخدمين بحيث لا يمكن الوصول إليها إلا لهذا المستخدم داخل النص البرمجي للإضافة. ولا يمكن للمستخدمين الآخرين والنصوص البرمجية الأخرى الوصول إلى هذه الخصائص. يمكنك الاطّلاع على PropertiesService للحصول على مزيد من التفاصيل.

الرموز المميزة لإثبات الهوية

يمكنك استخدام رمز مميز لمعرّف Google كبيانات اعتماد لتسجيل الدخول إلى خدمتك. هذه إحدى وسائل تحقيق الدخول الموحّد. تم تسجيل دخول المستخدمين بالفعل إلى Google لأنهم في تطبيق مضيف Google.