[AWS Lambda × Slack pt.1] Slackのchallenge認証を判別して切り分ける

うちのゼミで活用しているSlackワークスペースは、表面上はもちろんゼミのために稼働しているのだが、その実プライベートチャンネルになっているだけで僕の服薬管理からスケジュール、我が家のスマートホームの自動化まで僕個人の生活のかなりの部分を担っている。Slackの有料版を使っているわけでもなければ何かをゼミ経費で落としているわけでもないので別にいい。

SiriもAlexaも本当に使い物にならないのでとりあえずテキストベースで自分でぽちぽち作っているわけですが(基本的にはPythonのslack-sdkがかなり使いやすいので、AWS Chatbotとかわざわざ使わなくとも普通にスクラッチで作れる)、コアはSlackというよりAWSなので将来的にはAlexaとも連携して声でも操作できるようにします。

チャレンジ認証

Slackに色々やらせるためには大前提としてチャレンジ認証(url_verification event)を通さないといけない。Lambda上からトリガーとして設定できるAPI Gatewayで作成したAPIエンドポイントにSlackからPOSTを投げる。その辺の設定はSlack上ではEvent Subscriptionsから可能。そしてLambda上のevent_handler関数で受け取る。

そのあたりの設定は以下の解説なんかがわかりやすい。

SlackとAWSを繋ぐはじめの一歩~SlackのChallenge認証へチャレンジする~ https://dev.classmethod.jp/articles/challenge-about-challenge-request-for-slack/

ただし、この記事にも書いてあるとおりevent[‘challenge’]でchallengeの中身を読み出せるっていろんなところに書いてあるんだけど、CloudWatch上のログに吐き出したeventの中身を見る限り何をどう考えてもそれでは読めない。俺のだけなんか違うのだろうか。渡されるeventオブジェクトをprint(event)でそのまま見る限り、

{
    "version": "2.0",
    ...
    "headers": {
        "accept": "*/*",
        "accept-encoding": "gzip,deflate",
        "content-length": "129",
        ...
    },
    "requestContext": {
        ...
        "stage": "default",
        "time": "18/Jul/2021:10:52:02 +0000",
        "timeEpoch": 1626605522199
    },
    "body": "{\"token\":\"<MY_TOKEN_STRING_HERE>\",\"challenge\":\"<CHALLENGE_STRING_HERE>\",\"type\":\"url_verification\"}",
    "isBase64Encoded": false
}

という構造になっており、要はchallengeキーはあるにはあるがそもそもbodyの下にいるし、直のevent[‘challenge’]では読めるはずがない。しかも何がどうなったのかeventの大半の部分はjsonで飛んできてdict型になっているのだけれど肝心のchallengeが格納されたbodyの中身だけは文字列になっている。

つまりここでやるべきはまずeventからbodyを取り出し、さらにその中身をパースしてchallengeの文字列だけ取り出すことなので、受け取ってすぐchallengeの中身だけ返すなら

import json
def lambda_handler(event, context):
    return json.loads(event['body'])['challenge']

となる。

challenge認証が飛んできた時にはさっさとこれだけ返せばいいわけなので、lambda_handlerでeventを受け取った時点でまずchallenge認証かどうかを判別すればよくて、ということはtypeがurl_verificationかどうかを見ればいいんだけど、しかしながら本当はこの判別は上にもあるとおり

if event['body']['type'] == 'url_verification':
    return ~

とかいきなりやれれば楽なんだけど、如何せんbodyの中身が文字列なので、

import json
def is_challenge(event):
    if 'body' in event:
        body = json.loads(event['body'])
        if ('type' in body) & (body['type'] == 'url_verification'):
            return body['challenge']

def lambda_handler(event, context):
    # Check if the challenge or not
    clg = is_challenge(event = event)
    if clg:
        return clg

とか書かざるを得ない気がする。

これでとりあえずchallenge認証はほかっといても通るようになるので後は他の機能を載せていけばよい。

続きはまた気が向いたときに書く。