BigQuery row-level security with Advanced Services
Stay organized with collections
Save and categorize content based on your preferences.
For example, let's assume you are trying to create a solution for your sales
organization. The sales organization is divided into multiple regions where each
region has its own Regional Manager. The sales dataset in BigQuery has sales
amount by region and by date.
The proposed solution will have a single dashboard where the Regional Managers
can view sales data only for their own regions.
Requirements
Dashboard viewers will be signed-in with a Google account.
A mapping is available between the user's email and the data/rows they have
access to.
A service account will be used to access BigQuery data. Thus the billing
will be centralized and managed by the dashboard provider.
Limitations
The dashboard requires a one-time authorization from each viewer on first
view.
Viewers cannot edit the dashboard or share with others.
If you are a Workspace customer and your administrator has disabled sharing
Drive files to “Anyone with a Link”, either remove the sharing
restriction or develop the solution on a Gmail.com account.
Solution
Complete all the following steps to implement the solution.
View Manifest reference and complete the manifest with all required
information including the following:
set dataStudio.forceViewersCredentials to true.
set dataStudio.advancedServices.data to true.
For oauthScopes add https://www.googleapis.com/auth/userinfo.email and
https://www.googleapis.com/auth/script.external_request. See Authorization
Scopes for Apps Script for more info.
Conditional: Add all relevant scopes for the services used in the
connector.
Implement the required OAuth2 code for the service account:
varSERVICE_ACCOUNT_CREDS='SERVICE_ACCOUNT_CREDS';varSERVICE_ACCOUNT_KEY='private_key';varSERVICE_ACCOUNT_EMAIL='client_email';varBILLING_PROJECT_ID='project_id';/** * Copy the entire credentials JSON file from creating a service account in GCP. */functiongetServiceAccountCreds(){returnJSON.parse(scriptProperties.getProperty(SERVICE_ACCOUNT_CREDS));}functiongetOauthService(){varserviceAccountCreds=getServiceAccountCreds();varserviceAccountKey=serviceAccountCreds[SERVICE_ACCOUNT_KEY];varserviceAccountEmail=serviceAccountCreds[SERVICE_ACCOUNT_EMAIL];returnOAuth2.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']);}
Implement getData()
Construct your BigQuery query.
Using the email, lookup the mapping between the email and the data.
Optional: Use a URL shortener service to create a short-link for the
dashboard URL. Share the shortened URL with your users. This helps to replace
the dashboard URL later if needed.
The following is a complete example of the connector code:
main.js
varcc=DataStudioApp.createCommunityConnector();varscriptProperties=PropertiesService.getScriptProperties();functionisAdminUser(){returntrue;}functiongetAuthType(){varAuthTypes=cc.AuthType;returncc.newAuthTypeResponse().setAuthType(AuthTypes.NONE).build();}functiongetConfig(request){varconfig=cc.getConfig();config.newInfo().setId('generalInfo').setText('This is an example connector to showcase row level security.');returnconfig.build();}functiongetFields(){varfields=cc.getFields();vartypes=cc.FieldType;varaggregations=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);returnfields;}functiongetSchema(request){return{schema:getFields().build()};}varSERVICE_ACCOUNT_CREDS='SERVICE_ACCOUNT_CREDS';varSERVICE_ACCOUNT_KEY='private_key';varSERVICE_ACCOUNT_EMAIL='client_email';varBILLING_PROJECT_ID='project_id';/** * Copy the entire credentials JSON file from creating a service account in GCP. */functiongetServiceAccountCreds(){returnJSON.parse(scriptProperties.getProperty(SERVICE_ACCOUNT_CREDS));}functiongetOauthService(){varserviceAccountCreds=getServiceAccountCreds();varserviceAccountKey=serviceAccountCreds[SERVICE_ACCOUNT_KEY];varserviceAccountEmail=serviceAccountCreds[SERVICE_ACCOUNT_EMAIL];returnOAuth2.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']);}varBASE_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';functiongetData(request){varaccessToken=getOauthService().getAccessToken();varserviceAccountCreds=getServiceAccountCreds();varbillingProjectId=serviceAccountCreds[BILLING_PROJECT_ID];varemail=Session.getEffectiveUser().getEmail();varbqTypes=DataStudioApp.createCommunityConnector().BigQueryParameterType;returncc.newBigQueryConfig().setAccessToken(accessToken).setBillingProjectId(billingProjectId).setUseStandardSql(true).setQuery(BASE_SQL).addQueryParameter('email',bqTypes.STRING,email).build();}
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2024-09-18 UTC."],[[["\u003cp\u003eThis solution enables creating a BigQuery dashboard where viewers see only their relevant data based on a pre-defined mapping.\u003c/p\u003e\n"],["\u003cp\u003eIt leverages a service account for centralized billing and requires a one-time authorization from each viewer.\u003c/p\u003e\n"],["\u003cp\u003eThe implementation involves creating a Community Connector, setting up a service account, and constructing a BigQuery query that filters data based on the viewer's email.\u003c/p\u003e\n"],["\u003cp\u003eThe dashboard can be shared with users via a link, and usage can be optionally measured using Google Analytics.\u003c/p\u003e\n"]]],[],null,["| **Objective:** A viewer of a BigQuery dashboard will see only the data relevant for them.\n\nFor example, let's assume you are trying to create a solution for your sales\norganization. The sales organization is divided into multiple regions where each\nregion has its own Regional Manager. The sales dataset in BigQuery has sales\namount by region and by date.\n\nThe proposed solution will have a single dashboard where the Regional Managers\ncan view sales data only for their own regions.\n\nRequirements\n\n- Dashboard viewers will be signed-in with a Google account.\n- A mapping is available between the user's email and the data/rows they have access to.\n- A service account will be used to access BigQuery data. Thus the billing will be centralized and managed by the dashboard provider.\n\nLimitations\n\n- The dashboard requires a one-time authorization from each viewer on first view.\n- Viewers cannot edit the dashboard or share with others.\n- If you are a Workspace customer and your administrator has disabled sharing Drive files to \"Anyone with a Link\", either remove the sharing restriction or develop the solution on a Gmail.com account.\n\nSolution\n\nComplete all the following steps to implement the solution.\n\nCreate a new Community Connector\n\nReview [How Community Connectors Work](https://youtu.be/1u1wOD3RjSA?list=PLIivdWyY5sqLNJttHVnNtjKVgt2PGF4Js&t=15) and complete the [Community Connector\nCodelab](/looker-studio/connector/get-started) to get started. Use the [Developer tool for creating connectors](/looker-studio/connector/local-development) for a\nfaster and easier development process.\n\nWrite the connector code\n\n1. `getAuthType()` should return `NONE`.\n2. `getConfig()` should return an empty config.\n - *Optional:* If you need specific inputs for configuring the dashboard, you can request user input here.\n3. `getSchema()` should return the schema for your query.\n - *Optional:* You can add custom fields and calculations either in the SQL query or using [calculated fields](/looker-studio/connector/calculated-fields) as part of the schema.\n4. `getData()` will be completed at [a later step](#implement-getData).\n\nUpdate manifest\n\nView [Manifest reference](/looker-studio/connector/manifest) and complete the manifest with all required\ninformation including the following:\n\n1. set `dataStudio.forceViewersCredentials` to `true`.\n2. set `dataStudio.advancedServices.data` to `true`.\n3. For `oauthScopes` add `https://www.googleapis.com/auth/userinfo.email` and `https://www.googleapis.com/auth/script.external_request`. See [Authorization\n Scopes for Apps Script](/apps-script/concepts/scopes) for more info.\n - *Conditional:* Add all relevant scopes for the services used in the connector.\n\nThe manifest should look like this: \n\n {\n ...\n \"dataStudio\": {\n \"forceViewersCredentials\": true,\n \"advancedServices\": {\n \"data\": true\n },\n ...\n }\n \"oauthScopes\": [\n \"https://www.googleapis.com/auth/script.external_request\",\n \"https://www.googleapis.com/auth/userinfo.email\"\n ],\n ...\n }\n\nImplement a service account\n\n1. [Create a service account](https://cloud.google.com/iam/docs/creating-managing-service-accounts) in your Google Cloud project. This will be your billing project.\n2. Ensure this service account has BigQuery access in the cloud project.\n - Required Identity and Access Management (IAM) Roles: `BigQuery Data Viewer`, `BigQuery Job User`\n3. Download the JSON file to get the [service accounts keys](https://cloud.google.com/iam/docs/creating-managing-service-account-keys). Store the keys in your connector project's [script properties](/apps-script/reference/properties/properties-service#getScriptProperties).\n4. [Include](/apps-script/guides/libraries#gaining_access_to_a_library_and_including_it_in_your_project) the [OAuth2 for Apps Script](https://github.com/googleworkspace/apps-script-oauth2) library in your Apps Script project.\n5. Implement the required OAuth2 code for the service account: \n\n ```transact-sql\n var SERVICE_ACCOUNT_CREDS = 'SERVICE_ACCOUNT_CREDS';\n var SERVICE_ACCOUNT_KEY = 'private_key';\n var SERVICE_ACCOUNT_EMAIL = 'client_email';\n var BILLING_PROJECT_ID = 'project_id';\n\n /**\n * Copy the entire credentials JSON file from creating a service account in GCP.\n */\n function getServiceAccountCreds() {\n return JSON.parse(scriptProperties.getProperty(SERVICE_ACCOUNT_CREDS));\n }\n\n function getOauthService() {\n var serviceAccountCreds = getServiceAccountCreds();\n var serviceAccountKey = serviceAccountCreds[SERVICE_ACCOUNT_KEY];\n var serviceAccountEmail = serviceAccountCreds[SERVICE_ACCOUNT_EMAIL];\n\n return OAuth2.createService('RowLevelSecurity')\n .setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')\n .setTokenUrl('https://accounts.google.com/o/oauth2/token')\n .setPrivateKey(serviceAccountKey)\n .setIssuer(serviceAccountEmail)\n .setPropertyStore(scriptProperties)\n .setCache(CacheService.getScriptCache())\n .setScope(['https://www.googleapis.com/auth/bigquery.readonly']);\n }\n ```\n\nImplement `getData()`\n\n1. Construct your BigQuery query.\n - Using the email, lookup the mapping between the email and the data.\n - Use JOIN and/or WHERE clause to filter the data.\n2. Get the effective user's email ([user identity reference](/looker-studio/connector/user-identity)).\n3. Use the [Looker Studio Advanced Services](/looker-studio/connector/advanced-services) to return the query configuration from getData.\n - Pass the constructed query, the billing project, and the Service Account OAuth token.\n - *Conditional:* If you are taking user input via the connector `getConfig`, you should incorporate the input as [BigQuery parameters](https://cloud.google.com/bigquery/docs/parameterized-queries).\n\nCreate the dashboard\n\n1. Understand how [deployments and versions](/looker-studio/connector/deploy) work for connectors.\n2. [Create a production deployment](/looker-studio/connector/deploy) for the connector.\n3. Use the Production deployment to create a data source and a new report in Looker Studio.\n4. Add all tables and charts in the report.\n5. The dashboard is now ready to be shared with your users.\n\nMake the dashboard available to users\n\n1. [Share the connector script](https://support.google.com/docs/answer/2494822#link_sharing) with selected users or with \"Anyone with link\".\n2. [Share the dashboard](https://support.google.com/looker-studio/answer/6287179) with selected users or with \"Anyone with link\".\n3. *Optional:* Use a URL shortener service to create a short-link for the dashboard URL. Share the shortened URL with your users. This helps to replace the dashboard URL later if needed.\n4. *Optional:* [Measure dashboard usage](https://support.google.com/looker-studio/answer/7410792) by setting up Google Analytics for your report.\n\nExample Code\n\nThe following is a complete example of the connector code:\n\nmain.js \n\n```transact-sql\nvar cc = DataStudioApp.createCommunityConnector();\nvar scriptProperties = PropertiesService.getScriptProperties();\n\nfunction isAdminUser() {\n return true;\n}\n\nfunction getAuthType() {\n var AuthTypes = cc.AuthType;\n return cc\n .newAuthTypeResponse()\n .setAuthType(AuthTypes.NONE)\n .build();\n}\n\nfunction getConfig(request) {\n var config = cc.getConfig();\n\n config\n .newInfo()\n .setId('generalInfo')\n .setText('This is an example connector to showcase row level security.');\n\n return config.build();\n}\n\nfunction getFields() {\n var fields = cc.getFields();\n var types = cc.FieldType;\n var aggregations = cc.AggregationType;\n\n fields\n .newDimension()\n .setId('region')\n .setName('Region')\n .setType(types.TEXT);\n\n fields\n .newMetric()\n .setId('sales')\n .setName('Sales')\n .setType(types.NUMBER)\n .setAggregation(aggregations.SUM);\n\n fields\n .newDimension()\n .setId('date')\n .setName('Date')\n .setType(types.YEAR_MONTH_DAY);\n\n return fields;\n}\n\nfunction getSchema(request) {\n return {schema: getFields().build()};\n}\n\nvar SERVICE_ACCOUNT_CREDS = 'SERVICE_ACCOUNT_CREDS';\nvar SERVICE_ACCOUNT_KEY = 'private_key';\nvar SERVICE_ACCOUNT_EMAIL = 'client_email';\nvar BILLING_PROJECT_ID = 'project_id';\n\n/**\n * Copy the entire credentials JSON file from creating a service account in GCP.\n */\nfunction getServiceAccountCreds() {\n return JSON.parse(scriptProperties.getProperty(SERVICE_ACCOUNT_CREDS));\n}\n\nfunction getOauthService() {\n var serviceAccountCreds = getServiceAccountCreds();\n var serviceAccountKey = serviceAccountCreds[SERVICE_ACCOUNT_KEY];\n var serviceAccountEmail = serviceAccountCreds[SERVICE_ACCOUNT_EMAIL];\n\n return OAuth2.createService('RowLevelSecurity')\n .setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')\n .setTokenUrl('https://accounts.google.com/o/oauth2/token')\n .setPrivateKey(serviceAccountKey)\n .setIssuer(serviceAccountEmail)\n .setPropertyStore(scriptProperties)\n .setCache(CacheService.getScriptCache())\n .setScope(['https://www.googleapis.com/auth/bigquery.readonly']);\n}\n\nvar BASE_SQL =\n 'SELECT d.region, d.sales, d.date ' +\n 'FROM `datastudio-solutions.row_level_security.data` d ' +\n 'INNER JOIN `datastudio-solutions.row_level_security.access` a ' +\n 'ON d.region = a.region ' +\n 'where a.email=@email';\n\nfunction getData(request) {\n var accessToken = getOauthService().getAccessToken();\n var serviceAccountCreds = getServiceAccountCreds();\n var billingProjectId = serviceAccountCreds[BILLING_PROJECT_ID];\n var email = Session.getEffectiveUser().getEmail();\n\n var bqTypes = DataStudioApp.createCommunityConnector().BigQueryParameterType;\n\n return cc\n .newBigQueryConfig()\n .setAccessToken(accessToken)\n .setBillingProjectId(billingProjectId)\n .setUseStandardSql(true)\n .setQuery(BASE_SQL)\n .addQueryParameter('email', bqTypes.STRING, email)\n .build();\n}\n```"]]