Skip to content

Commit b4b5d14

Browse files
committed
Update WebhookHandler, update document, and add tests
1 parent e6b69b2 commit b4b5d14

File tree

3 files changed

+133
-6
lines changed

3 files changed

+133
-6
lines changed

README.rst

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,18 @@ WebhookParser
111111

112112
※ You can use WebhookParser
113113

114-
\_\_init\_\_(self, channel\_secret)
115-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
114+
\_\_init\_\_(self, channel\_secret, skip\_signature\_verification=lambda: False)
115+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
116116

117117
.. code:: python
118118
119119
parser = linebot.v3.WebhookParser('YOUR_CHANNEL_SECRET')
120120
121+
# or with skip_signature_verification
122+
parser = linebot.v3.WebhookParser(
123+
'YOUR_CHANNEL_SECRET',
124+
skip_signature_verification=lambda: True # or a function that returns a boolean
125+
121126
parse(self, body, signature, as_payload=False)
122127
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
123128
@@ -143,13 +148,18 @@ WebhookHandler
143148
144149
※ You can use WebhookHandler
145150
146-
\_\_init\_\_(self, channel\_secret)
147-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
151+
\_\_init\_\_(self, channel\_secret, skip\_signature\_verification=lambda: False)
152+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
148153
149154
.. code:: python
150155
151156
handler = linebot.v3.WebhookHandler('YOUR_CHANNEL_SECRET')
152157
158+
# or with skip_signature_verification
159+
handler = linebot.v3.WebhookHandler(
160+
'YOUR_CHANNEL_SECRET',
161+
skip_signature_verification=lambda: True # or a function that returns a boolean
162+
153163
handle(self, body, signature)
154164
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
155165

linebot/v3/webhook.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,13 @@ class WebhookHandler(object):
155155
Please read https://github.com/line/line-bot-sdk-python#webhookhandler
156156
"""
157157

158-
def __init__(self, channel_secret):
158+
def __init__(self, channel_secret, skip_signature_verification = lambda: False):
159159
"""__init__ method.
160160
161161
:param str channel_secret: Channel secret (as text)
162+
:param skip_signature_verification: (optional) Function that returns a boolean value whether to skip signature validation
162163
"""
163-
self.parser = WebhookParser(channel_secret)
164+
self.parser = WebhookParser(channel_secret, skip_signature_verification)
164165
self._handlers = {}
165166
self._default = None
166167

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
from __future__ import unicode_literals, absolute_import
16+
17+
import unittest
18+
19+
from linebot.v3 import (
20+
WebhookParser, WebhookHandler
21+
)
22+
from linebot.v3.exceptions import InvalidSignatureError
23+
24+
class TestWebhookParserWithSkipSignatureVerification(unittest.TestCase):
25+
def setUp(self):
26+
self.parser = WebhookParser('channel_secret')
27+
self.parser_with_skip = WebhookParser('channel_secret', skip_signature_verification=lambda: True)
28+
29+
def test_parse_with_invalid_signature(self):
30+
body = '{"events": []}'
31+
signature = 'invalid_signature'
32+
33+
# Should raise InvalidSignatureError when skip_signature_verification is False (default)
34+
with self.assertRaises(InvalidSignatureError):
35+
self.parser.parse(body, signature)
36+
37+
# Should not raise InvalidSignatureError when skip_signature_verification is True
38+
try:
39+
self.parser_with_skip.parse(body, signature)
40+
except InvalidSignatureError:
41+
self.fail("parse() raised InvalidSignatureError unexpectedly!")
42+
43+
class TestWebhookHandlerWithSkipSignatureVerification(unittest.TestCase):
44+
def setUp(self):
45+
self.handler = WebhookHandler('channel_secret')
46+
self.handler_with_skip = WebhookHandler('channel_secret', skip_signature_verification=lambda: True)
47+
self.handler_called = False
48+
self.handler_with_skip_called = False
49+
50+
@self.handler.default()
51+
def default(event):
52+
self.handler_called = True
53+
54+
@self.handler_with_skip.default()
55+
def default_with_skip(event):
56+
self.handler_with_skip_called = True
57+
58+
def test_handle_with_invalid_signature(self):
59+
body = '{"events": [{"type": "message", "message": {"type": "text", "id": "123", "text": "test"}, "timestamp": 1462629479859, "source": {"type": "user", "userId": "U123"}, "replyToken": "reply_token", "mode": "active", "webhookEventId": "test_id", "deliveryContext": {"isRedelivery": false}}]}'
60+
signature = 'invalid_signature'
61+
62+
# Should raise InvalidSignatureError when skip_signature_verification is False (default)
63+
with self.assertRaises(InvalidSignatureError):
64+
self.handler.handle(body, signature)
65+
66+
# Handler should not be called when signature verification fails
67+
self.assertFalse(self.handler_called)
68+
69+
# Should not raise InvalidSignatureError when skip_signature_verification is True
70+
try:
71+
self.handler_with_skip.handle(body, signature)
72+
except InvalidSignatureError:
73+
self.fail("handle() raised InvalidSignatureError unexpectedly!")
74+
75+
# Handler should be called when signature verification is skipped
76+
self.assertTrue(self.handler_with_skip_called)
77+
78+
def test_dynamic_skip_signature_verification(self):
79+
body = '{"events": [{"type": "message", "message": {"type": "text", "id": "123", "text": "test"}, "timestamp": 1462629479859, "source": {"type": "user", "userId": "U123"}, "replyToken": "reply_token", "mode": "active", "webhookEventId": "test_id", "deliveryContext": {"isRedelivery": false}}]}'
80+
signature = 'invalid_signature'
81+
skip_flag = [False]
82+
83+
# Create a handler with dynamic skip flag
84+
handler_with_dynamic_skip = WebhookHandler(
85+
'channel_secret',
86+
skip_signature_verification=lambda: skip_flag[0]
87+
)
88+
89+
dynamic_handler_called = [False]
90+
91+
@handler_with_dynamic_skip.default()
92+
def default_dynamic(event):
93+
dynamic_handler_called[0] = True
94+
95+
# Should raise InvalidSignatureError when skip_signature_verification returns False
96+
with self.assertRaises(InvalidSignatureError):
97+
handler_with_dynamic_skip.handle(body, signature)
98+
99+
# Handler should not be called
100+
self.assertFalse(dynamic_handler_called[0])
101+
102+
# Change the skip flag
103+
skip_flag[0] = True
104+
105+
# Should not raise InvalidSignatureError now
106+
try:
107+
handler_with_dynamic_skip.handle(body, signature)
108+
except InvalidSignatureError:
109+
self.fail("handle() raised InvalidSignatureError unexpectedly!")
110+
111+
# Handler should be called now
112+
self.assertTrue(dynamic_handler_called[0])
113+
114+
if __name__ == '__main__':
115+
unittest.main()
116+

0 commit comments

Comments
 (0)