ربط إضافة Google Workspace بخدمة خارجية

بطاقة تفويض مخصّصة من معاينة رابط تتضمّن شعار الشركة
  ووصفًا وزر تسجيل الدخول

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

توضّح هذه الصفحة كيفية مصادقة المستخدمين باستخدام عملية تفويض (مثل OAuth)، والتي تتضمّن الخطوات التالية:

  1. رصد الحالات التي يلزم فيها الحصول على تفويض
  2. عرض واجهة بطاقة تطلب من المستخدمين تسجيل الدخول إلى الخدمة
  3. أعِد تحميل الإضافة حتى يتمكّن المستخدمون من الوصول إلى الخدمة أو المورد المحمي.

إذا كانت الإضافة لا تتطلّب سوى هوية المستخدم، يمكنك مصادقة المستخدمين مباشرةً باستخدام رقم تعريف Google Workspace أو عنوان بريدهم الإلكتروني. لاستخدام عنوان البريد الإلكتروني للمصادقة، اطّلِع على التحقّق من صحة طلبات JSON. إذا أنشأت الإضافة باستخدام "برمجة تطبيقات Google"، يمكنك تسهيل هذه العملية باستخدام مكتبة OAuth2 لـ "برمجة تطبيقات Google" (تتوفّر أيضًا إصدار OAuth1).

رصد أنّ التفويض مطلوب

عند استخدام الإضافة، قد لا يكون المستخدمون مفوَّضين بالوصول إلى مورد محمي لأسباب متنوعة، مثل الآتي:

  • لم يتم إنشاء رمز مميّز للوصول إلى الخدمة التابعة لجهة خارجية حتى الآن أو انتهت صلاحيته.
  • لا يشمل رمز الوصول المورد المطلوب.
  • لا يشمل رمز الوصول النطاقات المطلوبة للطلب.

من المفترض أن ترصد الإضافة هذه الحالات لكي يتمكّن المستخدمون من تسجيل الدخول والوصول إلى خدمتك.

إذا كنت تنشئ تطبيقًا باستخدام Apps Script، يمكن أن تخبرك دالة مكتبة OAuth hasAccess() ما إذا كان بإمكانك الوصول إلى خدمة. بدلاً من ذلك، عند استخدام طلبات UrlFetchApp fetch() ، يمكنك ضبط المَعلمة muteHttpExceptions على true. ويؤدي ذلك إلى منع الطلب من طرح استثناء في حال تعذّر تنفيذه، كما يتيح لك فحص رمز استجابة الطلب ومحتواه في عنصر HttpResponse الذي تم إرجاعه.

مطالبة المستخدمين بتسجيل الدخول إلى خدمتك

عندما ترصد الإضافة أنّ التفويض مطلوب، يجب أن تعرِض الإضافة واجهة بطاقة لطلب تسجيل دخول المستخدمين إلى الخدمة. يجب أن تعيد بطاقة تسجيل الدخول توجيه المستخدمين لإكمال عملية المصادقة والتفويض من خلال الجهة الخارجية على البنية الأساسية.

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

إنشاء بطاقة تسجيل الدخول وإرسالها

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

بطاقة التفويض الأساسية

تعرض الصورة التالية مثالاً لبطاقة التفويض الأساسية من Google:

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

تعرِض التعليمة البرمجية التالية مثالاً على استخدام بطاقة التفويض الأساسية من Google:

برمجة تطبيقات

CardService.newAuthorizationException()
    .setAuthorizationUrl('AUTHORIZATION_URL')
    .setResourceDisplayName('RESOURCE_DISPLAY_NAME')
    .throwException();

JSON

عرض استجابة JSON التالية:

{
  "basic_authorization_prompt": {
    "authorization_url": "AUTHORIZATION_URL",
    "resource": "RESOURCE_DISPLAY_NAME"
  }
}

غيِّر القيم في السلسلة على الشكل التالي:

  • AUTHORIZATION_URL: عنوان URL لتطبيق الويب الذي يتولى التفويض
  • RESOURCE_DISPLAY_NAME: الاسم المعروض للمورد أو الخدمة المحمية يتم عرض هذا الاسم للمستخدم في طلب التفويض. على سبيل المثال، إذا كان RESOURCE_DISPLAY_NAME هو Example Account، ستظهر الرسالة التالية: "تريد هذه الإضافة عرض معلومات إضافية، ولكنها تحتاج إلى موافقة للوصول إلى حسابك على Example."

بعد إكمال عملية التفويض، يُطلَب من المستخدم إعادة تحميل الإضافة للوصول إلى المورد المحمي.

بطاقة تفويض مخصّصة

لتعديل طلب التفويض، يمكنك إنشاء بطاقة مخصّصة لتجربة تسجيل الدخول إلى خدمتك.

إذا كنت تنشر الإضافة علنيًا، يجب استخدام بطاقة تفويض مخصّصة. لمزيد من المعلومات عن requirements publishing requirements for the Google Workspace Marketplace، يُرجى الاطّلاع على لمحة عن مراجعة التطبيقات.

يجب أن تستوفي البطاقة المُسترَدة الشروط التالية:

  • يجب توضيح للمستخدم أنّ الإضافة تطلب إذنًا بالوصول إلى خدمة غير تابعة لشركة Google نيابةً عنه.
  • يجب توضيح الإجراءات التي يمكن للإضافة تنفيذها في حال منح الإذن لها.
  • أن تحتوي على زر أو تطبيق مصغّر مشابه ينقل المستخدم إلى عنوان URL للتفويض في الخدمة تأكَّد من أنّ وظيفة التطبيق المصغّر هذه واضحة للمستخدم.
  • يجب أن تستخدم الأداة المصغّرة أعلاه الإعداد OnClose.RELOAD في عنصرOpenLink لضمان إعادة تحميل الإضافة بعدتلقّي الإذن.
  • يجب أن تستخدم جميع الروابط التي يتم فتحها من طلب التفويض بروتوكول HTTPS.

تعرض الصورة التالية مثالاً لبطاقة تفويض مخصّصة لصفحة هبوط إضافة. تتضمّن البطاقة شعارًا ووصفًا وزر تسجيل الدخول:

بطاقة تفويض مخصّصة لشركة Cymbal Labs تتضمّن شعار الشركة
  ووصفًا وزرًا لتسجيل الدخول.

توضِّح التعليمة البرمجية التالية كيفية استخدام مثال البطاقة المخصّصة هذا:

برمجة تطبيقات

function customAuthorizationCard() {
    let cardSection1Image1 = CardService.newImage()
        .setImageUrl('LOGO_URL')
        .setAltText('LOGO_ALT_TEXT');

    let cardSection1Divider1 = CardService.newDivider();

    let cardSection1TextParagraph1 = CardService.newTextParagraph()
        .setText('DESCRIPTION');

    let cardSection1ButtonList1Button1 = CardService.newTextButton()
        .setText('Sign in')
        .setBackgroundColor('#0055ff')
        .setTextButtonStyle(CardService.TextButtonStyle.FILLED)
        .setAuthorizationAction(CardService.newAuthorizationAction()
            .setAuthorizationUrl('AUTHORIZATION_URL'));

    let cardSection1ButtonList1 = CardService.newButtonSet()
        .addButton(cardSection1ButtonList1Button1);

    let cardSection1TextParagraph2 = CardService.newTextParagraph()
        .setText('TEXT_SIGN_UP');

    let cardSection1 = CardService.newCardSection()
        .addWidget(cardSection1Image1)
        .addWidget(cardSection1Divider1)
        .addWidget(cardSection1TextParagraph1)
        .addWidget(cardSection1ButtonList1)
        .addWidget(cardSection1TextParagraph2);

    let card = CardService.newCardBuilder()
        .addSection(cardSection1)
        .build();
    return [card];
}

function startNonGoogleAuth() {
    CardService.newAuthorizationException()
        .setAuthorizationUrl('AUTHORIZATION_URL')
        .setResourceDisplayName('RESOURCE_DISPLAY_NAME')
        .setCustomUiCallback('customAuthorizationCard')
        .throwException();
  }

JSON

عرض استجابة JSON التالية:

{
  "custom_authorization_prompt": {
    "action": {
      "navigations": [
        {
          "pushCard": {
            "sections": [
              {
                "widgets": [
                  {
                    "image": {
                      "imageUrl": "LOGO_URL",
                      "altText": "LOGO_ALT_TEXT"
                    }
                  },
                  {
                    "divider": {}
                  },
                  {
                    "textParagraph": {
                      "text": "DESCRIPTION"
                    }
                  },
                  {
                    "buttonList": {
                      "buttons": [
                        {
                          "text": "Sign in",
                          "onClick": {
                            "openLink": {
                              "url": "AUTHORIZATION_URL",
                              "onClose": "RELOAD",
                              "openAs": "OVERLAY"
                            }
                          },
                          "color": {
                            "red": 0,
                            "green": 0,
                            "blue": 1,
                            "alpha": 1,
                          }
                        }
                      ]
                    }
                  },
                  {
                    "textParagraph": {
                      "text": "TEXT_SIGN_UP"
                    }
                  }
                ]
              }
            ]
          }
        }
      ]
    }
  }
}

غيِّر القيم في السلسلة على الشكل التالي:

  • LOGO_URL: عنوان URL لشعار أو صورة يجب أن يكون عنوان URL متاحًا للجميع.
  • LOGO_ALT_TEXT: النص البديل للشعار أو الصورة، مثل Cymbal Labs Logo.
  • DESCRIPTION: عبارة تحث المستخدمين على تسجيل الدخول، مثل Sign in to get started.
  • لتعديل زر تسجيل الدخول:
    • AUTHORIZATION_URL: عنوان URL لتطبيق الويب الذي يتولى التفويض
    • اختياري: لتغيير لون الزر، عدِّل قيم RGBA العشرية في الحقل color. بالنسبة إلى Apps Script، عدِّل الوسيطة setBackgroundColor() باستخدام القيم الست عشرية.
  • TEXT_SIGN_UP: نص يطلب من المستخدمين إنشاء حساب إذا لم يكن لديهم حساب مثلاً: New to Cymbal Labs? <a href=\"https://www.example.com/signup\">Sign up</a> here

إدارة عمليات تسجيل الدخول إلى تطبيقات الجهات الخارجية في جميع تطبيقات Google Workspace

من التطبيقات الشائعة لإضافات Google Workspace هو توفير واجهة للتفاعل مع نظام تابع لجهة خارجية من داخل تطبيق مضيف في Google Workspace.

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

إذا كنت بصدد إنشاء تطبيق باستخدام Apps Script، يمكنك منع تكرار طلبات تسجيل الدخول باستخدام خصائص المستخدمين أو الرموز المميّزة للتعريف. وسيتم شرح ذلك في الأقسام التالية.

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

يمكنك تخزين بيانات تسجيل دخول المستخدم في سمات المستخدِم في Apps Script. على سبيل المثال، يمكنك إنشاء رمز JSON المميّز للويب (JWT) من خدمة تسجيل الدخول وتسجيله في خاصيّة مستخدم، أو تسجيل اسم المستخدم وكلمة المرور لخدمته.

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

الرموز المميّزة لتعريف المستخدمين

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

مثال على إعداد OAuth غير التابع لشركة Google

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

برمجة تطبيقات

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