通过高级服务确保 BigQuery 行级安全性

例如,假设您正在尝试为您的销售组织制定解决方案。销售组织分布在多个区域,其中每个区域都有自己的区域经理。BigQuery 中的销售数据集包含按区域和按日期划分的销售额。

所提议的解决方案将只有一个信息中心,区域经理只能在其中查看自己区域的销售数据。

要求

  • 信息中心查看者应使用 Google 账号登录。
  • 用户的电子邮件地址与其有权访问的数据/行之间存在对应关系。
  • 可使用一个服务账号来访问 BigQuery 数据。因此,结算将集中进行,并由信息中心提供商管理。

限制

  • 信息中心要求每位查看者在首次查看时提供一次性授权。
  • 查看者不得修改信息中心或与他人共享。
  • 如果您是 Workspace 客户,并且您的管理员已停用共享功能 将云端硬盘文件设为“知道链接的任何人”,也可以取消共享 限制 Gmail.com 账号,或制定解决方案。

解决方案

完成以下所有步骤以实现解决方案。

创建一个新的社区连接器

查看社区连接器的工作原理并完成社区连接器 Codelab 以开始使用。使用用于创建连接器的开发者工具,实现更快捷、更轻松的开发流程。

编写连接器代码

  1. getAuthType() 应返回 NONE
  2. getConfig() 应返回空的配置。
    • 可选:如果您需要特定输入来配置信息中心,可以在此处请求用户输入。
  3. getSchema() 应返回查询的架构。
    • 可选:您可以在 SQL 查询中添加自定义字段和计算,也可以将计算字段用作架构的一部分。
  4. getData() 将在稍后的步骤中完成。

更新清单

查看清单参考,并使用所有必需的信息完成清单,包括:

  1. dataStudio.forceViewersCredentials 设置为 true
  2. dataStudio.advancedServices.data 设置为 true
  3. 对于 oauthScopes,添加 https://www.googleapis.com/auth/userinfo.emailhttps://www.googleapis.com/auth/script.external_request。如需了解详情,请参阅 Apps 脚本的授权范围
    • 条件:为连接器中使用的服务添加所有相关范围。

清单应大致如下所示:

{
 
...
 
"dataStudio": {
   
"forceViewersCredentials": true,
   
"advancedServices": {
       
"data": true
   
},
   
...
 
}
 
"oauthScopes": [
   
"https://www.googleapis.com/auth/script.external_request",
   
"https://www.googleapis.com/auth/userinfo.email"
   
],
 
...
}

实现服务账号

  1. 在 Google Cloud 项目中创建一个服务账号。这将是您的结算项目。
  2. 确保此服务账号在 Cloud 项目中具有 BigQuery 访问权限。
    • 必需的身份和访问权限管理 (IAM) 角色:BigQuery Data ViewerBigQuery Job User
  3. 下载 JSON 文件以获取服务账号密钥。将密钥存储在连接器项目的脚本属性中。
  4. 在 Apps 脚本项目中添加适用于 Apps 脚本的 OAuth2 库。
  5. 为服务账号实现所需的 OAuth2 代码:
    var SERVICE_ACCOUNT_CREDS = 'SERVICE_ACCOUNT_CREDS';
    var SERVICE_ACCOUNT_KEY = 'private_key';
    var SERVICE_ACCOUNT_EMAIL = 'client_email';
    var BILLING_PROJECT_ID = 'project_id';

    /**
     * Copy the entire credentials JSON file from creating a service account in GCP.
     */

    function getServiceAccountCreds() {
     
    return JSON.parse(scriptProperties.getProperty(SERVICE_ACCOUNT_CREDS));
    }

    function getOauthService() {
     
    var serviceAccountCreds = getServiceAccountCreds();
     
    var serviceAccountKey = serviceAccountCreds[SERVICE_ACCOUNT_KEY];
     
    var serviceAccountEmail = serviceAccountCreds[SERVICE_ACCOUNT_EMAIL];

     
    return OAuth2.createService('RowLevelSecurity')
       
    .setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
       
    .setTokenUrl('https://accounts.google.com/o/oauth2/token')
       
    .setPrivateKey(serviceAccountKey)
       
    .setIssuer(serviceAccountEmail)
       
    .setPropertyStore(scriptProperties)
       
    .setCache(CacheService.getScriptCache())
       
    .setScope(['https://www.googleapis.com/auth/bigquery.readonly']);
    }

实现 getData()

  1. 构建 BigQuery 查询。
    • 使用电子邮件地址,查询电子邮件地址与数据之间的对应关系。
    • 使用 JOIN 和/或 WHERE 子句过滤数据。
  2. 获取用户的有效电子邮件地址(用户身份参考)。
  3. 使用 Looker Studio 高级服务返回查询配置 。
    • 传递已构建的查询、结算项目和服务账号 OAuth 令牌。
    • 条件:如果您要通过连接器 getConfig 获取用户输入,则应将输入整合为 BigQuery 参数

创建信息中心

  1. 了解对于连接器而言部署和版本的运作方式。
  2. 为连接器创建生产部署
  3. 使用生产部署在 Looker Studio。
  4. 在报告中添加所有表格和图表。
  5. 现在,您可以与用户共享信息中心了。

向用户提供信息中心访问权限

  1. 与所选用户或“知道链接的任何人”共享连接器脚本
  2. 与所选用户或“知道链接的任何人”共享信息中心
  3. 可选:使用网址缩短服务为信息中心网址创建一个短链接。与您的用户共享该缩短的网址。这便于以后替换信息中心网址(如果需要)。
  4. 可选:通过为报告设置 Google Analytics 来衡量信息中心的使用情况

示例代码

main.js

var cc = DataStudioApp.createCommunityConnector();
var scriptProperties = PropertiesService.getScriptProperties();

function isAdminUser() {
 
return true;
}

function getAuthType() {
 
var AuthTypes = cc.AuthType;
 
return cc
   
.newAuthTypeResponse()
   
.setAuthType(AuthTypes.NONE)
   
.build();
}

function getConfig(request) {
 
var config = cc.getConfig();

  config
   
.newInfo()
   
.setId('generalInfo')
   
.setText('This is an example connector to showcase row level security.');

 
return config.build();
}

function getFields() {
 
var fields = cc.getFields();
 
var types = cc.FieldType;
 
var aggregations = cc.AggregationType;

  fields
   
.newDimension()
   
.setId('region')
   
.setName('Region')
   
.setType(types.TEXT);

  fields
   
.newMetric()
   
.setId('sales')
   
.setName('Sales')
   
.setType(types.NUMBER)
   
.setAggregation(aggregations.SUM);

  fields
   
.newDimension()
   
.setId('date')
   
.setName('Date')
   
.setType(types.YEAR_MONTH_DAY);

 
return fields;
}

function getSchema(request) {
 
return {schema: getFields().build()};
}

var SERVICE_ACCOUNT_CREDS = 'SERVICE_ACCOUNT_CREDS';
var SERVICE_ACCOUNT_KEY = 'private_key';
var SERVICE_ACCOUNT_EMAIL = 'client_email';
var BILLING_PROJECT_ID = 'project_id';

/**
 * Copy the entire credentials JSON file from creating a service account in GCP.
 */

function getServiceAccountCreds() {
 
return JSON.parse(scriptProperties.getProperty(SERVICE_ACCOUNT_CREDS));
}

function getOauthService() {
 
var serviceAccountCreds = getServiceAccountCreds();
 
var serviceAccountKey = serviceAccountCreds[SERVICE_ACCOUNT_KEY];
 
var serviceAccountEmail = serviceAccountCreds[SERVICE_ACCOUNT_EMAIL];

 
return OAuth2.createService('RowLevelSecurity')
   
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
   
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
   
.setPrivateKey(serviceAccountKey)
   
.setIssuer(serviceAccountEmail)
   
.setPropertyStore(scriptProperties)
   
.setCache(CacheService.getScriptCache())
   
.setScope(['https://www.googleapis.com/auth/bigquery.readonly']);
}

var BASE_SQL =
 
'SELECT d.region, d.sales, d.date ' +
 
'FROM `datastudio-solutions.row_level_security.data` d ' +
 
'INNER JOIN `datastudio-solutions.row_level_security.access` a ' +
 
'ON d.region = a.region ' +
 
'where a.email=@email';

function getData(request) {
 
var accessToken = getOauthService().getAccessToken();
 
var serviceAccountCreds = getServiceAccountCreds();
 
var billingProjectId = serviceAccountCreds[BILLING_PROJECT_ID];
 
var email = Session.getEffectiveUser().getEmail();

 
var bqTypes = DataStudioApp.createCommunityConnector().BigQueryParameterType;

 
return cc
   
.newBigQueryConfig()
   
.setAccessToken(accessToken)
   
.setBillingProjectId(billingProjectId)
   
.setUseStandardSql(true)
   
.setQuery(BASE_SQL)
   
.addQueryParameter('email', bqTypes.STRING, email)
   
.build();
}