Google Apps Scriptで作成したコネクタやGoogleデータポータルで作成したデータソースとレポートは、すべてGoogleの共有設定によりアクセス権限を細かく設定できます。とりあえず、現在はオーナーの私だけが権限となっているので他からのアクセスされることに心配はありません。一方、Googleデータポータル:コミュニティコネクタ(1)コントローラの作成において作成したコントローラは、この状態では誰でもアクセスできるコントローラとなっています。良くないですね。どうかしてみましょう!

コントローラで認証

コントローラとGoogleデータポータルデータソースで秘密のキーを共有し、データソースがコントローラにアクセスするときに、そのキーを渡してコントローラで認証とします。秘密のキーは一般にAPIのアクセスに使われる、APIキーと同じことです。キーを知らなければ誰もアクセスできません。

まず、環境設定の.envのファイルを変更します。

...
GDP_KEY=N2JjNDc4MTk2M2Q1MGRlMTY3NGIxOGE3

N2JjNDc4MTk2M2Q1MGRlMTY3NGIxOGE3が秘密のキーです。

このキーは、config('app.gdp_key')として取得するので、config/app.phpファイルの編集も必要です。

<?php

return [
...
        'Validator' => Illuminate\Support\Facades\Validator::class,
        'View' => Illuminate\Support\Facades\View::class,

    ],

    'gdp_key' => env('GDP_KEY'),
];

そしてコントローラでデータソースから渡される認証キーをチェックします。キーがない、キーが間違っているなら403の禁止のエラーとなります。

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\User;

class GdpController extends Controller
{
    /**
     * index
     *
     * @param  \Illuminate\Http\Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function index(Request $request)
    {
        DB::enableQueryLog();
                                                                                                                                                                                
       // 秘密のキーをチェック
        if ((! $request->key) || ($request->key != config('app.gdp_key'))) {
            abort(403);
        }
                                                                                                                                                                                                              
        $query = User::query();                                                                                                                                                                                    
                                                                                                                                                                                                                   
        if ($request->date_start) {                                                                                                                                                                                
            $query->where('created_at', '>=', $request->date_start);                                                                                                                                               
        }                                                                                                                                                                                                          
                                                                                                                                                                                                                   
        if ($request->date_end) {                                                                                                                                                                                  
            $query->where('created_at', '<=', $request->date_end);                                                                                                                                                 
        }                                                                                                                                                                                                          
                                                                                                                                                                                                                   
        $rows = $query                                                                                                                                                                                             
            ->select(DB::raw('id, DATE(created_at) as date_created'))                                                                                                                             
            ->orderBy('created_at')                                                                                                                                                                                
            ->get();                                                                                                                                                                                               
                                                                                                                                                                                                                   
        Log::debug(DB::getQueryLog());   
    }
}

早速、テストしてみましょう。まずは、秘密キーなしでテストします。

$ php artisan tinker
Psy Shell v0.9.9 (PHP 7.2.16 — cli) by Justin Hileman
>>> use Zttp\Zttp;
>>> $url = 'https://example.com/l58/gdp';
=> "https://example.com/l58/gdp"
>>> $r = Zttp::post($url, ['date_start' => '2019-01-01', 'date_end' => '2019-01-10']);
=> Zttp\ZttpResponse {#2956
     +"response": GuzzleHttp\Psr7\Response {#3001},
     +"cookies": GuzzleHttp\Cookie\CookieJar {#2957},
     +"transferStats": GuzzleHttp\TransferStats {#3002},
   }
>>> $r->status();
=> 403

アクセス禁止のステータスです。次は、秘密キーありでテストします。

>>> $r = Zttp::post($url, ['date_start' => '2019-01-01', 'date_end' => '2019-01-10', 'key' => 'N2JjNDc4MTk2M2Q1MGRlMTY3NGIxOGE3']);
=> Zttp\ZttpResponse {#2997
     +"response": GuzzleHttp\Psr7\Response {#3063},
     +"cookies": GuzzleHttp\Cookie\CookieJar {#3008},
     +"transferStats": GuzzleHttp\TransferStats {#3064},
   }
>>> $r->status();
=> 200
>>> $r->json();
=> [
     [
       "id" => 17,
       "date_created" => "2019-01-06",
     ],
     [
       "id" => 95,
       "date_created" => "2019-01-07",
     ],
     [
       "id" => 73,
       "date_created" => "2019-01-08",
     ],
   ]

認証成功です!

データソースで秘密のキーを設定

今度は、Googleデータポータルでデータソースの作成する際に、カスタムコネクタを選択したところで、アクセスするURLだけではなく秘密キーも尋ねるようにします。

これには、Google Apps Scriptのカスタムコネクタのプログラムの変更が必要です。以下全コードを掲載します。前回のコードgetConfig()と比べてみてください。fetchDataFromApi()の関数の定義に変更があります。

var cc = DataStudioApp.createCommunityConnector();

// 認証に必要な関数。ここでは認証なし。

function getAuthType() {
  var response = { type: 'NONE' };
  return response;
}

// このコネクタを使用してデータソースを作成するときに指定するときに必要な関数

function getConfig(request) {
  var config = cc.getConfig();
  
  config
    .newTextInput()
    .setId('url')
    .setName('データ元のURLを指定してください')
    .setHelpText('e.g. https://example.com/gdp')
    .setPlaceholder('https://');
  
  config
    .newTextInput()
    .setId('key')
    .setName('秘密のキーを入力してください'); // 秘密のキーを尋ねる
  
  config.setDateRangeRequired(true);

  return config.build();
}

// データソースで表示される項目を定義

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

  fields
    .newMetric()
    .setId('id')
    .setName('id')
    .setType(types.NUMBER);

  fields
    .newDimension()
    .setId('date_created')
    .setName('date_created')
    .setType(types.YEAR_MONTH_DAY);
  
  fields
    .newDimension()
    .setId('year_month')
    .setName('年月')
    .setFormula("TODATE(date_created,'%Y-%m')")
    .setType(types.YEAR_MONTH);

  return fields;
}

// データソースで表示される項目に必要な関数

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

// データソースを通して実際のデータ取得のために必要な関数

function getData(request) {

  var requestedFields = getFields().forIds(
    request.fields.map(function(field) {
      return field.name;
    })
  );
  
  try {
    var response = fetchDataFromApi(request);
    
    var rows = JSON.parse(response);
    
    var data = [];
    
    rows.forEach(function(row) {
      var values = [];
      requestedFields.asArray().forEach(function(field) {
        switch(field.getId()) {
          case 'id':
            values.push(row.id);
            break;
          case 'date_created':
            values.push(row.date_created);
            break;            
          default:
            values.push('');
        }
      });
      data.push({
        values: values
      });
    });
  } catch (e) {
    cc.newUserError()
      .setDebugText('Error fetching data from API. Exception details: ' + e)
      .setText(
        'The connector has encountered an unrecoverable error. Please try again later, or file an issue if this error persists.'
      )
      .throwException();
  }

  return {
    schema: requestedFields.build(),
    rows: data
  };
}

/**
 * コントローラにアクセスして、jsonのデータを取得。開始日と終了日の期間も指定する。
 *
 * @param {Object} request Data request parameters.
 * @returns {string} Response text for UrlFetchApp.
 */
function fetchDataFromApi(request) {
 var formData = {
    'key' : request.configParams.key,   //秘密のキーを送る
    'date_start' : request.dateRange.startDate,
    'date_end' : request.dateRange.endDate
  };

  var options = {
    'method' : 'get',
    'payload' : formData
  };

  var response = UrlFetchApp.fetch(request.configParams.url, options);
  return response;
}

// 以下を設定すると、エラーが起きたときに詳細の説明が得られる

function isAdminUser() {
  return true;
}

データソースとレポートの再設定

コネクタのコードを保存したら、今度はGoogleデータポータルで、データソースを作成し直します。

既存のコネクタを使用するなら、初期化が必要なので、現在使用しているコネクタのアクセス権限を外します。

そこからは、Googleデータポータル:コミュニティコネクタ(3)データソースとレポートの作成の、コネクタの登録とデータソースの作成のステップに従ってください。

前回と違って、以下のように秘密のキーを入力する必要あります。再接続ボタンを押してデータソースの変更は完了です。

今度は、前回作成したGoogleデータポータルのレポートを開いて編集です。オープンすると以下のよにデータソースのエラーとなっています。上の変更でレポートのデータソースが更新されたからです。以下のように前回のデータソースは、「不明」となっているので、更新したデータソースを代わりに選択します。

以前と同じグラフが出てきました。これで完了です。

By khino