CORDEA blog

Android applications engineer

Reports API で G Suite 監査ログの更新通知を受け取る

G Suite の Admin console には監査ログというものがあり、そこで色々見たり Alert を上げたりすることが出来るのですが、
G Suite Admin SDKReports API を使用すると、この監査ログを取得することが出来ます
また、Callback URL を登録することで更新通知を受け取ることも出来ます。

ということでこの記事では Meet の更新通知を受け取ってみます。
Meet は非対応ですが、主要なイベントだと Alert Center でメールに飛ばせるのでメールならそちらのほうが良いでしょう。

注意

監査ログはリアルタイムに通知されるわけではなく、利用できるようになるまでに一定の時間がかかります。また、この時間はサービスによって異なります。
Meet はほぼリアルタイムに更新されるようですが、私が見ていたところでは監査ログに表示されるまでに概ね 3~5 分程度かかっていました。

Data retention and lag times - G Suite Admin Help

また、更新通知の受け取りに関してですが、最初は届いていたのですが何度も試していたせいか途中から届かなくなってしまいました。(同期用の Message は届くが監査ログの更新は通知されない)
Stackoverflow でもいくつか同様の投稿が見受けられましたので、使用する際には注意したほうが良いかもしれません。

個人で契約している G Suite Basic なので、Business とか Enterprise だと違ったりするのかもしれません。

実装

実装したやつ置いときます

github.com

通知を受け取る Web API の用意

とりあえず通知が POST されるので、その先を予め用意しておく必要があります。

Flask だと以下のようになります。

@app.route('/events', methods=['POST'])
def events():
  headers = request.headers

if __name__ == '__main__':
  app.run(debug=True)

まず、ここにはいくつかの Message が POST されますが、そのうちの一つに同期用の Sync Message があります。

これは登録後に POST される確認用の Message です。
この Message には登録解除用の ID などが含まれていますが、監査ログとしての情報はないため、ここでは無視します。

@app.route('/events', methods=['POST'])
def events():
  headers = request.headers
  if headers['X-Goog-Resource-State'] == 'sync':
    return {}

Header に含まれる情報はここに書いてあります。

あとは通知の Parse です。
POST される json には種別や ID などが含まれますが、おそらく大体の人が必要としている情報は events にあります。
配下は配列となっており、その中身には Event type, name と Event の parameter が含まれています。
Event の parameter についてはそれぞれの Reference に記載があります。
Meet の場合は以下

Hangouts Meet Audit Activity Events  |  Reports API  |  Google Developers

あとは欲しい情報を取得してなんかいい感じにするだけです。

@app.route('/events', methods=['POST'])
def events():
  headers = request.headers
  if headers['X-Goog-Resource-State'] == 'sync':
    return {}
  for event in request.json['events']:
    print(event['name'])

Deploy する

さきほど用意した Web API をどこか見えるところに Deploy します。
Callback URL に登録するためにはいくつか条件があります。

  • https であること
    • 自己署名などは不可です
  • Search console で domain の所有権が確認できること

私の場合は Google App Engine に deploy しました。
Repository に app.yaml を commit しておいたので同じように試すときは gcloud app deploy で試すことが出来ます。

Domain を登録する

Google API Console にアクセスし、必要なら任意の Project を選択します (無いなら作る)。
Domain verification から Add domain を選択し
Callback URL の domain を入力します。
この時、所有権が確認できていない場合は Seach Console での domain 登録を求められますので、その通りにします。
私は meta tag で行ったので Repository に index.html.example として commit してあります。

Reports API を有効化する

おそらく Admin SDK という名前になっているのですが、これを有効化して credentials.json を持ってくる必要があります。

Python Quickstart  |  Reports API  |  Google Developers

Callback URL を登録する

Activities: watch  |  Reports API  |  Google Developers

あとは Callback URL を登録するだけです。
Python だったらこのへんを参考にします。

Python Quickstart  |  Reports API  |  Google Developers

def __get_google_service():
    creds = None
    if os.path.exists(TOKEN_PICKLE):
        with open(TOKEN_PICKLE, 'rb') as token:
            creds = pickle.load(token)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json',
                SCOPES
            )
            creds = flow.run_local_server(port=0)
        with open(TOKEN_PICKLE, 'wb') as token:
            pickle.dump(creds, token)
    return build('admin', 'reports_v1', credentials=creds)


def __register():
    response = __get_google_service().activities().watch(
        userKey='all',
        applicationName='meet',
        body={
            'address': settings.CALLBACK_URL,
            'type': 'web_hook',
            'id': ID
        }
    ).execute()

applicationName は今回は Meet の Event を取得するため 'meet' にします。Calendar の場合は 'calendar' です。
userKey は特定の User に絞りたい場合は使用します。今回は全て見たいので 'all' とします。

Response で得られる Resource ID と ID は保持するなり覚えておくなりすると登録解除が出来ます。
登録状態を見る API は無いため、Resource ID と ID がどこかへ行ってしまうと期限が切れる (たぶん 6 時間) のを待つ以外にありません。

登録するとしばらくして Sync Message が届くはずです。

登録を解除する

Channels: stop  |  Reports API  |  Google Developers

登録の解除は以下のように行います。

__get_google_service().channels().stop(
    body={
        'id': id,
        'resourceId': resource_id
    }
).execute()

が、どうやら Python の Client library だとこの stop API の URL が間違っているようなので Issue を参考に書き換えます。

channels._baseUrl = channels._baseUrl.replace('/admin/reports/v1', '')

なお Issue では AuthorizedSession を使う方法も提案されていて、そちらのほうが良さげです。


おわり