Skip to content

Commit 07603ac

Browse files
author
Ace Nassri
authored
functions/slack: message verification - tokens -> signatures (#2588)
* functions/slack: message verification - tokens -> signatures * Add main.py * Fix failing tests * Add secrets to Kokoro, take 1 * Add secrets, take 2 * Fix tests, take 2 * Fix the fix * Fix ^ 3 * Fix ^ 4 * Address comments
1 parent 13fdf72 commit 07603ac

File tree

6 files changed

+41
-13
lines changed

6 files changed

+41
-13
lines changed

.kokoro/tests/run_tests.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ source ./testing/test-env.sh
4040
export GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/testing/service-account.json
4141
export GOOGLE_CLIENT_SECRETS=$(pwd)/testing/client-secrets.json
4242
source "${KOKORO_GFILE_DIR}/automl_secrets.txt"
43+
cp "${KOKORO_GFILE_DIR}/functions-slack-config.json" "functions/slack/config.json"
4344

4445
# For Datalabeling samples to hit the testing endpoint
4546
export DATALABELING_ENDPOINT="test-datalabeling.sandbox.googleapis.com:443"

functions/slack/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"SLACK_TOKEN": "YOUR_SLACK_TOKEN",
2+
"SLACK_SECRET": "YOUR_SLACK_SIGNING_SECRET",
33
"KG_API_KEY": "YOUR_KG_API_KEY"
44
}

functions/slack/main.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# limitations under the License.
1313

1414
# [START functions_slack_setup]
15+
import hashlib
16+
import hmac
1517
import json
1618
import os
1719

@@ -26,13 +28,25 @@
2628
kgsearch = apiclient.discovery.build(
2729
'kgsearch',
2830
'v1',
29-
developerKey=os.environ['API_KEY'] or config['KG_API_KEY'])
31+
developerKey=os.environ.get('API_KEY') or config.get('KG_API_KEY'),
32+
cache_discovery=False)
3033
# [END functions_slack_setup]
3134

3235

3336
# [START functions_verify_webhook]
34-
def verify_web_hook(form):
35-
if not form or form.get('token') != config['SLACK_TOKEN']:
37+
# Python 3+ version of https://github.com/slackapi/python-slack-events-api/blob/master/slackeventsapi/server.py
38+
def verify_signature(request):
39+
timestamp = request.headers.get('X-Slack-Request-Timestamp', '')
40+
signature = request.headers.get('X-Slack-Signature', '')
41+
42+
req = str.encode('v0:{}:'.format(timestamp)) + request.get_data()
43+
request_digest = hmac.new(
44+
str.encode(config['SLACK_SECRET']),
45+
req, hashlib.sha256
46+
).hexdigest()
47+
request_hash = 'v0={}'.format(request_digest)
48+
49+
if not hmac.compare_digest(request_hash, signature):
3650
raise ValueError('Invalid request/credentials.')
3751
# [END functions_verify_webhook]
3852

@@ -92,7 +106,7 @@ def kg_search(request):
92106
if request.method != 'POST':
93107
return 'Only POST requests are accepted', 405
94108

95-
verify_web_hook(request.form)
109+
verify_signature(request)
96110
kg_search_response = make_search_request(request.form['text'])
97111
return jsonify(kg_search_response)
98112
# [END functions_slack_search]

functions/slack/main_test.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,30 @@
3131

3232

3333
class Request(object):
34-
def __init__(self):
35-
pass
34+
def __init__(self, data=b''):
35+
self.data = data
36+
37+
def get_data(self):
38+
return self.data
3639

3740

3841
class TestGCFPySlackSample(object):
39-
def test_verify_web_hook_request_form_empty(self):
42+
def test_verify_signature_request_form_empty(self):
4043
with pytest.raises(ValueError):
41-
main.verify_web_hook({})
44+
request = Request()
45+
request.headers = {}
46+
main.verify_signature(request)
4247

43-
def test_verify_web_hook_token_incorrect(self):
48+
def test_verify_signature_token_incorrect(self):
4449
with pytest.raises(ValueError):
45-
main.verify_web_hook({'token': 123})
50+
request = Request()
51+
request.headers = {'X-Slack-Signature': '12345'}
52+
main.verify_signature(request)
4653

4754
def test_verify_web_hook_valid_request(self):
48-
main.verify_web_hook({'token': config['SLACK_TOKEN']})
55+
request = Request()
56+
request.headers = {'X-Slack-Signature': os.environ['SLACK_TEST_SIGNATURE']}
57+
main.verify_signature(request)
4958

5059
def test_format_slack_message(self):
5160
message = main.format_slack_message('lion', example_response)
@@ -72,8 +81,10 @@ def test_kg_search(self):
7281
search.execute.return_value = example_response
7382
request = Request()
7483
request.method = 'POST'
84+
request.headers = {
85+
'X-Slack-Signature': os.environ['SLACK_TEST_SIGNATURE']
86+
}
7587
request.form = {
76-
'token': config['SLACK_TOKEN'],
7788
'text': 'lion'
7889
}
7990

functions/slack/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
google-api-python-client==1.7.11
22
flask==1.1.1
3+
oauth2client==4.1.3
4+
slackeventsapi==2.1.0

testing/secrets.tar.enc

-6 KB
Binary file not shown.

0 commit comments

Comments
 (0)