8000 Add end_on_invalid_message and fix timeout issue (#1339) · Adityagrao/botbuilder-python@c259ae7 · GitHub
[go: up one dir, main page]

Skip to content

Commit c259ae7

Browse files
Add end_on_invalid_message and fix timeout issue (microsoft#1339)
* Add end_on_invalid_message and fix timeout issue * fixing pylint Co-authored-by: Axel Suarez <axsuarez@microsoft.com>
1 parent 3105211 commit c259ae7

File tree

3 files changed

+175
-4
lines changed

3 files changed

+175
-4
lines changed

libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,13 +203,17 @@ async def continue_dialog(self, dialog_context: DialogContext) -> DialogTurnResu
203203
The prompt generally continues to receive the user's replies until it accepts the
204204
user's reply as valid input for the prompt.
205205
"""
206-
# Recognize token
207-
recognized = await self._recognize_token(dialog_context)
208-
209206
# Check for timeout
210207
state = dialog_context.active_dialog.state
211208
is_message = dialog_context.context.activity.type == ActivityTypes.message
212-
has_timed_out = is_message and (
209+
is_timeout_activity_type = (
210+
is_message
211+
or OAuthPrompt._is_token_response_event(dialog_context.context)
212+
or OAuthPrompt._is_teams_verification_invoke(dialog_context.context)
213+
or OAuthPrompt._is_token_exchange_request_invoke(dialog_context.context)
214+
)
215+
216+
has_timed_out = is_timeout_activity_type and (
213217
datetime.now() > state[OAuthPrompt.PERSISTED_EXPIRES]
214218
)
215219

@@ -221,6 +225,9 @@ async def continue_dialog(self, dialog_context: DialogContext) -> DialogTurnResu
221225
else:
222226
state["state"]["attemptCount"] += 1
223227

228+
# Recognize token
229+
recognized = await self._recognize_token(dialog_context)
230+
224231
# Validate the return value
225232
is_valid = False
226233
if self._validator is not None:
@@ -238,6 +245,9 @@ async def continue_dialog(self, dialog_context: DialogContext) -> DialogTurnResu
238245
# Return recognized value or re-prompt
239246
if is_valid:
240247
return await dialog_context.end_dialog(recognized.value)
248+
if is_message and self._settings.end_on_invalid_message:
249+
# If EndOnInvalidMessage is set, complete the prompt with no result.
250+
return await dialog_context.end_dialog(None)
241251

242252
# Send retry prompt
243253
if (

libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/oauth_prompt_settings.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def __init__(
1111
text: str = None,
1212
timeout: int = None,
1313
oauth_app_credentials: AppCredentials = None,
14+
end_on_invalid_message: bool = False,
1415
):
1516
"""
1617
Settings used to configure an `OAuthPrompt` instance.
@@ -22,9 +23,15 @@ def __init__(
2223
`OAuthPrompt` defaults value to `900,000` ms (15 minutes).
2324
oauth_app_credentials (AppCredentials): (Optional) AppCredentials to use for OAuth. If None,
2425
the Bots credentials are used.
26+
end_on_invalid_message (bool): (Optional) value indicating whether the OAuthPrompt should end upon
27+
receiving an invalid message. Generally the OAuthPrompt will ignore incoming messages from the
28+
user during the auth flow, if they are not related to the auth flow. This flag enables ending the
29+
OAuthPrompt rather than ignoring the user's message. Typically, this flag will be set to 'true',
30+
but is 'false' by default for backwards compatibility.
2531
"""
2632
self.connection_name = connection_name
2733
self.title = title
2834
self.text = text
2935
self.timeout = timeout
3036
self.oath_app_credentials = oauth_app_credentials
37+
self.end_on_invalid_message = end_on_invalid_message

libraries/botbuilder-dialogs/tests/test_oauth_prompt.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
ChannelAccount,
1010
ConversationAccount,
1111
InputHints,
12+
SignInConstants,
1213
TokenResponse,
1314
)
1415

@@ -260,3 +261,156 @@ async def callback_handler(turn_context: TurnContext):
260261

261262
await adapter.send("Hello")
262263
self.assertTrue(called)
264+
265+
async def test_should_end_oauth_prompt_on_invalid_message_when_end_on_invalid_message(
266+
self,
267+
):
268+
connection_name = "myConnection"
269+
token = "abc123"
270+
magic_code = "888999"
271+
272+
async def exec_test(turn_context: TurnContext):
273+
dialog_context = await dialogs.create_context(turn_context)
274+
275+
results = await dialog_context.continue_dialog()
276+
277+
if results.status == DialogTurnStatus.Empty:
278+
await dialog_context.prompt("prompt", PromptOptions())
279+
elif results.status == DialogTurnStatus.Complete:
280+
if results.result and results.result.token:
281+
await turn_context.send_activity("Failed")
282+
283+
else:
284+
await turn_context.send_activity("Ended")
285+
286+
await convo_state.save_changes(turn_context)
287+
288+
# Initialize TestAdapter.
289+
adapter = TestAdapter(exec_test)
290+
291+
# Create ConversationState with MemoryStorage and register the state as middleware.
292+
convo_state = ConversationState(MemoryStorage())
293+
294+
# Create a DialogState property, DialogSet and AttachmentPrompt.
295+
dialog_state = convo_state.create_property("dialog_state")
296+
dialogs = DialogSet(dialog_state)
297+
dialogs.add(
298+
OAuthPrompt(
299+
"prompt",
300+
OAuthPromptSettings(connection_name, "Login", None, 300000, None, True),
301+
)
302+
)
303+
304+
def inspector(
305+
activity: Activity, description: str = None
306+
): # pylint: disable=unused-argument
307+
assert len(activity.attachments) == 1
308+
assert (
309+
activity.attachments[0].content_type
310+
== CardFactory.content_types.oauth_card
311+
)
312+
313+
# send a mock EventActivity back to the bot with the token
314+
adapter.add_user_token(
315+
connection_name,
316+
activity.channel_id,
317+
activity.recipient.id,
318+
token,
319+
magic_code,
320+
)
321+
322+
step1 = await adapter.send("Hello")
323+
step2 = await step1.assert_reply(inspector)
324+
step3 = await step2.send("test invalid message")
325+
await step3.assert_reply("Ended")
326+
327+
async def test_should_timeout_oauth_prompt_with_message_activity(self,):
328+
activity = Activity(type=ActivityTypes.message, text="any")
329+
await self.run_timeout_test(activity)
330+
331+
async def test_should_timeout_oauth_prompt_with_token_response_event_activity(
332+
self,
333+
):
334+
activity = Activity(
335+
type=ActivityTypes.event, name=SignInConstants.token_response_event_name
336+
)
337+
await self.run_timeout_test(activity)
338+
339+
async def test_should_timeout_oauth_prompt_with_verify_state_operation_activity(
340+
self,
341+
):
342+
activity = Activity(
343+
type=ActivityTypes.invoke, name=SignInConstants.verify_state_operation_name
344+
)
345+
await self.run_timeout_test(activity)
346+
347+
async def test_should_not_timeout_oauth_prompt_with_custom_event_activity(self,):
348+
activity = Activity(type=ActivityTypes.event, name="custom event name")
349+
await self.run_timeout_test(activity, False, "Ended", "Failed")
350+
351+
async def run_timeout_test(
352+
self,
353+
activity: Activity,
354+
should_succeed: bool = True,
355+
token_response: str = "Failed",
356+
no_token_resonse="Ended",
357+
):
358+
connection_name = "myConnection"
359+
token = "abc123"
360+
magic_code = "888999"
361+
362+
async def exec_test(turn_context: TurnContext):
363+
dialog_context = await dialogs.create_context(turn_context)
364+
365+
results = await dialog_context.continue_dialog()
366+
367+
if results.status == DialogTurnStatus.Empty:
368+
await dialog_context.prompt("prompt", PromptOptions())
369+
elif results.status == DialogTurnStatus.Complete or (
370+
results.status == DialogTurnStatus.Waiting and not should_succeed
371+
):
372+
if results.result and results.result.token:
373+
await turn_context.send_activity(token_response)
374+
375+
else:
376+
await turn_context.send_activity(no_token_resonse)
377+
378+
await convo_state.save_changes(turn_context)
379+
380+
# Initialize TestAdapter.
381+
adapter = TestAdapter(exec_test)
382+
383+
# Create ConversationState with MemoryStorage and register the state as middleware.
384+
convo_state = ConversationState(MemoryStorage())
385+
386+
# Create a DialogState property, DialogSet and AttachmentPrompt.
387+
dialog_state = convo_state.create_property("dialog_state")
388+
dialogs = DialogSet(dialog_state)
389+
dialogs.add(
390+
OAuthPrompt(
391+
"prompt", OAuthPromptSettings(connection_name, "Login", None, 1),
392+
)
393+
)
394+
395+
def inspector(
396+
activity: Activity, description: str = None
397+
): # pylint: disable=unused-argument
398+
assert len(activity.attachments) == 1
399+
assert (
400+
activity.attachments[0].content_type
401+
== CardFactory.content_types.oauth_card
402+
)
403+
404+
# send a mock EventActivity back to the bot with the token
405+
adapter.add_user_token(
406+
connection_name,
407+
activity.channel_id,
408+
activity.recipient.id,
409+
token,
410+
magic_code,
411+
)
412+
< 48C1 code>413+
step1 = await adapter.send("Hello")
414+
step2 = await step1.assert_reply(inspector)
415+
step3 = await step2.send(activity)
416+
await step3.assert_reply(no_token_resonse)

0 commit comments

Comments
 (0)
0