סנכרון של הרשאות משתמשים (שילוב בצד השרת)

בעלי תוכן דיגיטלי משתמשים בשילוב בצד השרת בעיקר כדי לנהל את הקוראים ואת ההרשאות שלהם. בעלי תוכן דיגיטלי משתמשים ב-UpdateReaderEntitlements בעיקר כדי לעדכן את הרשומה של Google לגבי הרשאת מזהה המוצר ל-PPID.

הגדרה של Google Cloud

הגדרת קישור המינוי ב-Google Cloud כוללת שני רכיבים עיקריים:

  1. הפעלת ה-API בפרויקט נתון
  2. יצירת חשבון שירות לגישה ל-API

הפעלת Subscription Linking API

כדי להשתמש בחשבון שירות ולנהל את ההרשאות של הקוראים, צריך להפעיל בפרויקט ב-Google Cloud גם את Subscription Linking API וגם חשבון שירות OAuth שמוגדר כראוי. כדי להפעיל את Subscription Linking API בפרויקט, עוברים מהתפריט אל APIs & Services (API ושירותים) > Library (ספרייה) ומחפשים את Subscription Linking, או נכנסים ישירות לדף:


https://console.cloud.google.com/apis/library?project=gcp_project_id

api

איור 1. ניווט ל-API Library והפעלת ה-API בפרויקט ב-Google Cloud.

יצירת חשבון שירות

חשבונות שירות משמשים כדי לאפשר גישה מהאפליקציה שלכם ל-Subscription Linking API.

  1. יוצרים חשבון שירות במסוף של הפרויקט.
  2. יוצרים פרטי כניסה לחשבון השירות ושומרים את הקובץ credentials.json במיקום מאובטח שגלוי לאפליקציה.
  3. מקצים לחשבון השירות שיצרתם את התפקיד 'אדמין קישור מינויים' ב-IAM. כדי לשלוט באופן מפורט ביכולות של חשבון השירות, אפשר להקצות את התפקיד המתאים מהטבלה הבאה.
יכולת / תפקיד אדמין של קישור מינויים כלי הצפייה בקישור המינויים צפייה בהרשאות של קישור מינויים
אחזור הרשאות של קוראים
קבלת קוראים
עדכון ההרשאות של הקוראים
מחיקת קוראים

שימוש בחשבונות שירות עם Subscription Linking API

כדי לאמת קריאות ל-Subscription Linking API באמצעות חשבונות שירות, אפשר להשתמש בספריית הלקוח googleapis client library (שמטפלת באופן אוטומטי בבקשות access_token) או לחתום על הבקשות ישירות באמצעות ה-API ל-REST. אם משתמשים ב-API ל-REST, צריך קודם לקבל access_token (דרך ספריית האימות של Google או באמצעות אסימון JWT של חשבון שירות) ואז לכלול אותו בכותרת Authorization.

בדוגמאות הבאות ל-ספריית לקוח ול-API ל-REST מופיע קוד לדוגמה לקריאה ל-getReader(),‏ getReaderEntitlements(), ‏ updateReaderEntitlements() ו-deleteReader().

ספריית לקוח

בקטע הזה מוסבר איך להשתמש בספריית הלקוח של googleapis ב-Node.js.

בקשה לדוגמה

בשדה keyFile ב-constructor‏ Auth.GoogleAuth, מגדירים את הנתיב למפתח של חשבון השירות. אם אתם לא יכולים לייצא מפתח של חשבון שירות בגלל מדיניות הארגון, תוכלו להשתמש בשיטה פרטי כניסה שמוגדרים כברירת מחדל בחשבון (ADC). אם בוחרים בשיטת ADC, אין צורך לספק את השדה keyFile, כי ADC יחפש את פרטי הכניסה בעצמו.

import {readerrevenuesubscriptionlinking_v1, Auth} from 'googleapis';
const subscriptionLinking = readerrevenuesubscriptionlinking_v1.Readerrevenuesubscriptionlinking;

class SubscriptionLinking {
  constructor() {
    this.auth = new Auth.GoogleAuth({
      keyFile: process.env.KEY_FILE,
      scopes: [
        'https://www.googleapis.com/auth/readerrevenue.subscriptionlinking.manage'
      ],
    })
  }

  init() {
    return new subscriptionLinking(
        {version: 'v1', auth: this.auth})
  }
}

const subscriptionLinkingApi = new SubscriptionLinking();
const client = subscriptionLinkingApi.init();

/**
 * Retrieves details for a specific reader associated with the publication.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader.
 * @return {Promise<object>} A promise that resolves with the reader's details
 *  from the API.
 */
async function getReader(ppid) {
  const publicationId = process.env.PUBLICATION_ID;
  return await client.publications.readers.get({
    name: `publications/${publicationId}/readers/${ppid}`,
  });
};

/**
 * Updates the entitlements for a specific reader.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader whose
 *  entitlements are being updated.
 * @return {Promise<object>} A promise that resolves with the result of the
 *  updated entitlements object.
 */
async function updateReaderEntitlements(ppid) {
  const publicationId = process.env.PUBLICATION_ID;
  const requestBody = {
    /*
    Refer to
    https://developers.google.com/news/subscribe/subscription-linking/appendix/glossary#entitlements_object
    */
    entitlements : [{
      product_id: `${publicationId}:basic`,
      subscription_token: 'abc1234',
      detail: 'This is our basic plan',
      expire_time: '2025-10-21T03:05:08.200564Z'
    }]
  };
  return await client.publications.readers.updateEntitlements({
    name: `publications/${publicationId}/readers/${ppid}/entitlements`,
    requestBody
  });
};

/**
 * Retrieves the current entitlements for a specific reader.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader.
 * @return {Promise<object>} A promise that resolves with the reader's entitlements object.
 */
async function getReaderEntitlements(ppid) {
  const publicationId = process.env.PUBLICATION_ID;
  return await client.publications.readers.getEntitlements({
    name: `publications/${publicationId}/readers/${ppid}/entitlements`
  });
};

/**
 * Deletes a specific Subscription Linkikng reader record associated with the publication.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader to be deleted.
 * @param {boolean=} forceDelete - If true, delete the user even if their
 *  entitelements are not empty
 * @return {Promise<object>} A promise that resolves upon successful deletion
 *  with an empty object ({})
 */
async function deleteReader(ppid, forceDelete = false) {
  const publicationId = process.env.PUBLICATION_ID;
  return await client.publications.readers.delete({
    name: `publications/${publicationId}/readers/${ppid}`
    force: forceDelete
  });
};

API ל-REST

אם רוצים לקרוא לנקודות קצה של API ל-REST, אפשר להשתמש באחת מהשיטות כדי לקבל את accessToken ולהגדיר אותו בכותרת Authorization.

1. שימוש בספרייה GoogleAuth

למפתח credentials, אפשר להשתמש במפתח של חשבון שירות או בפרטי כניסה שמוגדרים כברירת מחדל בחשבון (ADC). אם בוחרים בשיטת ADC, אין צורך לספק את השדה credentials כי ה-ADC יחפש את פרטי הכניסה בעצמו.

import { GoogleAuth } from 'google-auth-library';
import credentialJson from 'path_to_your_json_file' with { type: 'json' };

const auth = new GoogleAuth({
    credentials: credential_json,
    scopes: [
      'https://www.googleapis.com/auth/readerrevenue.subscriptionlinking.manage'
    ]
});

async function getAccessToken() {
    const accessToken = await auth.getAccessToken();
    return accessToken;
}

2. יצירת access_token באמצעות JWT של חשבון שירות

import fetch from 'node-fetch';
import jwt from 'jsonwebtoken';

function getSignedJwt() {
  /*
    Either store the service account credentials string in an environmental variable
    Or implement logic to fetch it.
  */
  const key_file = process.env.CREDENTIALS_STRING

  const issueDate = new Date();
  const expireMinutes = 60;
  const offsetInSeconds = issueDate.getTimezoneOffset() * 60000;
  const expireDate = new Date(issueDate.getTime() + (expireMinutes * 60000));
  const iat = Math.floor((issueDate.getTime() + offsetInSeconds) / 1000);
  const exp = Math.floor((expireDate.getTime() + offsetInSeconds) / 1000);

  const token = {
    iss: key_file.client_email,
    iat,
    exp,
    aud: 'https://oauth2.googleapis.com/token',
    scope:'https://www.googleapis.com/auth/readerrevenue.subscriptionlinking.manage',
  }
  return jwt.sign(token, key_file.private_key, {
    algorithm: 'RS256',
    keyid: key_file.private_key_id,
  })
}

async function getAccessToken(signedJwt) {
  let body = new URLSearchParams();
  body.set('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
  body.set('assertion', signedJwt);
  const response = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    body
  })
  const accessResponse = await response.json();
  return accessResponse.access_token;
}

קוד לדוגמה לקריאות ל-API ל-REST באמצעות ספריית Google Auth

import { GoogleAuth } from 'google-auth-library';
import fetch from 'node-fetch'
import credentialJson from 'path_to_your_json_file' with { type: 'json' };

const BASE_SUBSCRIPTION_LINIING_API_URL='https://readerrevenuesubscriptionlinking.googleapis.com/v1';
const publicationId = process.env.PUBLICATION_ID

const auth = new GoogleAuth({
    credentials: credentialJson,
    scopes: [
      'https://www.googleapis.com/auth/readerrevenue.subscriptionlinking.manage'
    ]
});

async function getAccessToken() {
    const accessToken = await auth.getAccessToken();
    return accessToken;
}

/**
 * Retrieves details for a specific reader associated with the publication.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader.
 * @return {object} reader json for the given ppid
 */
async function getReader(ppid) {
  const endpoint = `${BASE_SUBSCRIPTION_LINIING_API_URL}/publications/${publicationId}/readers/${ppid}`;
  const accessToken = await getAccessToken();
  const response = await fetch(endpoint, {
     method: 'GET',
     headers: {
       Authorization: `Bearer ${accessToken}`,
     },
   });
  const reader = await response.json();
  return reader;
}

/**
 * Updates the entitlements for a specific reader.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader.
 * @return {object} the updated entitlements object in json.
 */
async function updateReaderEntitlements(ppid) {
  const endpoint = `${BASE_SUBSCRIPTION_LINIING_API_URL}/publications/${publicationId}/readers/${ppid}/entitlements`;
  const requestBody = {
    /*
    Refer to
    https://developers.google.com/news/subscribe/subscription-linking/appendix/glossary#entitlements_object
    */
    entitlements : [{
      product_id: `${publicationId}:basic`,
      subscription_token: 'abc1234',
      detail: 'This is our basic plan',
      expire_time: '2025-10-21T03:05:08.200564Z'
    }]
  };
  const response = await fetch(endpoint, {
     method: 'PATCH',
     headers: {
       Authorization: `Bearer ${accessToken}`,
       'Content-Type': 'application/json',
     },
     body: JSON.stringify(requestBody)
   })
  const updatedEntitlements = await response.json();
  return updatedEntitlements;
}

/**
 * Retrieves the current entitlements for a specific reader.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader.
 * @return {object} the reader's entitlements object in json.
 */
async function getReaderEntitlements(ppid) {
  const endpoint = `${BASE_SUBSCRIPTION_LINIING_API_URL}/publications/${publicationId}/readers/${ppid}/entitlements`;
  const accessToken = await getAccessToken();
  const response = await fetch(endpoint, {
     method: 'GET',
     headers: {
       Authorization: `Bearer ${accessToken}`,
     },
   });
  const entitlements = await response.json();
  return entitlements;
}

/**
 * Deletes a specific Subscription Linkikng reader record associated with the publication.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader.
 * @param {boolean=} forceDelete - If true, delete the user even if their
 *  entitelements are not empty
 * @return {object} returns an empty object ({}) if the delete operation is successful
 */
async function deleteReader(ppid, forceDelete = false) {
  const endpoint = `${BASE_SUBSCRIPTION_LINIING_API_URL}/publications/${publicationId}/readers/${ppid}?force=${forceDelete}`;
  const response = await fetch(endpoint, {
     method: 'DELETE',
     headers: {
       Authorization: `Bearer ${accessToken}`,
     }
   });
  const result = await response.json();
  return result;
}