Skip to content

Commit e09dc68

Browse files
port: enable LG syntax in OAuthInput (#3409)
* fixed a bug in expression parse to trim beginning '=' * fixed a bug in templates to recognize multiline correctly * updated OAuthPrompt to make sendOAuthCard() public static * Updated OAuthInput to enable LG syntax * added tests * make channelSupportOAuthCard() private * added inline comments * minor fix * Revert "minor fix" This reverts commit 09e30f0. Co-authored-by: Josh Gummersall <1235378+joshgummersall@users.noreply.github.com>
1 parent 728ed17 commit e09dc68

File tree

9 files changed

+548
-460
lines changed

9 files changed

+548
-460
lines changed

libraries/adaptive-expressions/src/expression.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ export class Expression {
227227
* @returns The expression object.
228228
*/
229229
public static parse(expression: string, lookup?: EvaluatorLookup): Expression {
230-
return new ExpressionParser(lookup || Expression.lookup).parse(expression);
230+
return new ExpressionParser(lookup || Expression.lookup).parse(expression.replace(/^=/, ''));
231231
}
232232

233233
/**
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"$schema": "../../../tests.schema",
3+
"$kind": "Microsoft.Test.Script",
4+
"description": "Test UserTokenMock set Property",
5+
"generator": "main.lg",
6+
"dialog": {
7+
"$kind": "Microsoft.AdaptiveDialog",
8+
"triggers": [
9+
{
10+
"$kind": "Microsoft.OnBeginDialog",
11+
"actions": [
12+
{
13+
"$kind": "Microsoft.OAuthInput",
14+
"connectionName": "magiccode x",
15+
"text": "${OAuthInputText()}",
16+
"title": "${OAuthInputTitle()}",
17+
"property": "dialog.token"
18+
}
19+
]
20+
}
21+
]
22+
},
23+
"script": [
24+
{
25+
"$kind": "Microsoft.Test.UserSays",
26+
"text": "hello"
27+
},
28+
{
29+
"$kind": "Microsoft.Test.AssertReplyActivity",
30+
"assertions": [
31+
"attachments[0].content.text == 'OAuth text set!'",
32+
"attachments[0].content.buttons[0].title == 'OAuth title set!'"
33+
]
34+
}
35+
]
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"$schema": "../../../tests.schema",
3+
"$kind": "Microsoft.Test.Script",
4+
"description": "Test UserTokenMock set Property",
5+
"dialog": {
6+
"$kind": "Microsoft.AdaptiveDialog",
7+
"triggers": [
8+
{
9+
"$kind": "Microsoft.OnBeginDialog",
10+
"actions": [
11+
{
12+
"$kind": "Microsoft.SetProperty",
13+
"property": "dialog.textProperty",
14+
"value": "Set the text!"
15+
},
16+
{
17+
"$kind": "Microsoft.SetProperty",
18+
"property": "dialog.titleProperty",
19+
"value": "Set the title!"
20+
},
21+
{
22+
"$kind": "Microsoft.OAuthInput",
23+
"connectionName": "magiccode x",
24+
"text": "=dialog.textProperty",
25+
"title": "=dialog.titleProperty",
26+
"property": "dialog.token"
27+
}
28+
]
29+
}
30+
]
31+
},
32+
"script": [
33+
{
34+
"$kind": "Microsoft.Test.UserSays",
35+
"text": "hello"
36+
},
37+
{
38+
"$kind": "Microsoft.Test.AssertReplyActivity",
39+
"assertions": [
40+
"attachments[0].content.text == 'Set the text!'",
41+
"attachments[0].content.buttons[0].title == 'Set the title!'"
42+
]
43+
}
44+
]
45+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# OAuthInputText()
2+
- OAuth text set!
3+
4+
# OAuthInputTitle()
5+
- OAuth title set!

libraries/botbuilder-dialogs-adaptive-testing/tests/testScript.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ describe('TestScriptTests', function () {
104104
await TestUtils.runTestScript(resourceExplorer, 'TestScriptTests_HttpRequestQnAMakerDialogMock');
105105
});
106106

107+
it('OAuthInputLG', async () => {
108+
await TestUtils.runTestScript(resourceExplorer, 'TestScriptTests_OAuthInputLG');
109+
});
110+
111+
it('OAuthInputMockProperties', async () => {
112+
await TestUtils.runTestScript(resourceExplorer, 'TestScriptTests_OAuthInputMockProperties');
113+
});
114+
107115
it('OAuthInputRetries_WithNullMessageText', async () => {
108116
await TestUtils.runTestScript(resourceExplorer, 'TestScriptTests_OAuthInputRetries_WithNullMessageText');
109117
});

libraries/botbuilder-dialogs-adaptive/src/input/oauthInput.ts

Lines changed: 24 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,11 @@ import {
1313
StringExpressionConverter,
1414
} from 'adaptive-expressions';
1515
import {
16-
ActionTypes,
1716
Activity,
1817
ActivityTypes,
19-
Attachment,
20-
BotAdapter,
21-
CardFactory,
22-
Channels,
2318
ExtendedUserTokenProvider,
2419
InputHints,
2520
IUserTokenProvider,
26-
MessageFactory,
27-
OAuthCard,
28-
OAuthLoginTimeoutKey,
2921
StatusCodes,
3022
TokenExchangeInvokeRequest,
3123
TokenResponse,
@@ -36,15 +28,18 @@ import {
3628
ConverterFactory,
3729
Dialog,
3830
DialogContext,
31+
DialogStateManager,
3932
DialogTurnResult,
33+
OAuthPrompt,
34+
OAuthPromptSettings,
4035
PromptOptions,
4136
PromptRecognizerResult,
4237
ThisPath,
4338
TurnPath,
4439
} from 'botbuilder-dialogs';
45-
import { SkillValidation } from 'botframework-connector';
46-
import { verifyStateOperationName, tokenExchangeOperationName, tokenResponseEventName } from 'botbuilder-core';
40+
import { verifyStateOperationName, tokenExchangeOperationName, tokenResponseEventName } from 'botbuilder';
4741
import { InputDialog, InputDialogConfiguration, InputState } from './inputDialog';
42+
import { TextTemplate } from '../templates';
4843

4944
export const channels: any = {
5045
console: 'console',
@@ -186,7 +181,7 @@ export class OAuthInput extends InputDialog implements OAuthInputConfiguration {
186181
dc.state.setValue(InputDialog.TURN_COUNT_PROPERTY, 1);
187182

188183
// Prompt user to login
189-
await this.sendOAuthCardAsync(dc, state.options.prompt);
184+
await this.sendOAuthCard(dc, state.options.prompt);
190185

191186
return Dialog.EndOfTurn;
192187
}
@@ -257,7 +252,7 @@ export class OAuthInput extends InputDialog implements OAuthInputConfiguration {
257252
}
258253

259254
if (isMessage) {
260-
await this.sendOAuthCardAsync(dc, promptOptions?.prompt);
255+
await this.sendOAuthCard(dc, promptOptions?.prompt);
261256
}
262257

263258
return Dialog.EndOfTurn;
@@ -351,98 +346,25 @@ export class OAuthInput extends InputDialog implements OAuthInputConfiguration {
351346
/**
352347
* @private
353348
*/
354-
private async sendOAuthCardAsync(dc: DialogContext, prompt?: string | Partial<Activity>): Promise<void> {
355-
const turnContext = dc.context;
356-
357-
// Validate adapter type
358-
if (!('getUserToken' in turnContext.adapter)) {
359-
throw new Error(`OAuthPrompt.sendOAuthCardAsync(): not supported for the current adapter.`);
360-
}
361-
362-
// Initialize outgoing message
363-
const msg: Partial<Activity> =
364-
typeof prompt === 'object'
365-
? { ...prompt }
366-
: MessageFactory.text(prompt, undefined, InputHints.AcceptingInput);
367-
if (!Array.isArray(msg.attachments)) {
368-
msg.attachments = [];
369-
}
370-
371-
// Add login card as needed
372-
if (this.channelSupportsOAuthCard(turnContext.activity.channelId)) {
373-
const cards: Attachment[] = msg.attachments.filter(
374-
(a: Attachment) => a.contentType === CardFactory.contentTypes.oauthCard
375-
);
376-
if (cards.length === 0) {
377-
let cardActionType = ActionTypes.Signin;
378-
const signInResource = await (turnContext.adapter as ExtendedUserTokenProvider).getSignInResource(
379-
turnContext,
380-
this.connectionName.getValue(dc.state),
381-
turnContext.activity.from.id
382-
);
383-
let link = signInResource.signInLink;
384-
const identity = turnContext.turnState.get((turnContext.adapter as BotAdapter).BotIdentityKey);
385-
386-
// use the SignInLink when
387-
// in speech channel or
388-
// bot is a skill or
389-
// an extra OAuthAppCredentials is being passed in
390-
if (
391-
(identity && SkillValidation.isSkillClaim(identity.claims)) ||
392-
this.isFromStreamingConnection(turnContext.activity)
393-
) {
394-
if (turnContext.activity.channelId === Channels.Emulator) {
395-
cardActionType = ActionTypes.OpenUrl;
396-
}
397-
} else if (!this.channelRequiresSignInLink(turnContext.activity.channelId)) {
398-
link = undefined;
399-
}
400-
401-
// Append oauth card
402-
const card = CardFactory.oauthCard(
403-
this.connectionName.getValue(dc.state),
404-
this.title.getValue(dc.state),
405-
this.text.getValue(dc.state),
406-
link,
407-
signInResource.tokenExchangeResource
408-
);
409-
410-
// Set the appropriate ActionType for the button.
411-
(card.content as OAuthCard).buttons[0].type = cardActionType;
412-
msg.attachments.push(card);
413-
}
414-
} else {
415-
const cards: Attachment[] = msg.attachments.filter(
416-
(a: Attachment) => a.contentType === CardFactory.contentTypes.signinCard
417-
);
418-
if (cards.length === 0) {
419-
// Append signin card
420-
const signInResource = await (turnContext.adapter as ExtendedUserTokenProvider).getSignInResource(
421-
turnContext,
422-
this.connectionName.getValue(dc.state),
423-
turnContext.activity.from.id
424-
);
425-
msg.attachments.push(
426-
CardFactory.signinCard(
427-
this.title.getValue(dc.state),
428-
signInResource.signInLink,
429-
this.text.getValue(dc.state)
430-
)
431-
);
432-
}
433-
}
434-
435-
// Add the login timeout specified in OAuthPromptSettings to TurnState so it can be referenced if polling is needed
436-
if (!turnContext.turnState.get(OAuthLoginTimeoutKey) && this.timeout) {
437-
turnContext.turnState.set(OAuthLoginTimeoutKey, this.timeout.getValue(dc.state));
349+
private async sendOAuthCard(dc: DialogContext, prompt?: string | Partial<Activity>): Promise<void> {
350+
let title: string =
351+
(await new TextTemplate<DialogStateManager>(this.title.expressionText).bind(dc, dc.state)) ??
352+
this.title.getValue(dc.state);
353+
if (title?.startsWith('=')) {
354+
title = Expression.parse(title).tryEvaluate(dc.state)?.value;
438355
}
439-
440-
if (!msg.inputHint) {
441-
msg.inputHint = InputHints.AcceptingInput;
356+
let text: string =
357+
(await new TextTemplate<DialogStateManager>(this.text.expressionText).bind(dc, dc.state)) ??
358+
this.text.getValue(dc.state);
359+
if (text?.startsWith('=')) {
360+
text = Expression.parse(text).tryEvaluate(dc.state)?.value;
442361
}
443-
444-
// Send prompt
445-
await turnContext.sendActivity(msg);
362+
const settings: OAuthPromptSettings = {
363+
connectionName: this.connectionName?.getValue(dc.state),
364+
title,
365+
text,
366+
};
367+
return OAuthPrompt.sendOAuthCard(settings, dc.context, prompt);
446368
}
447369

448370
/**
@@ -540,13 +462,6 @@ export class OAuthInput extends InputDialog implements OAuthInputConfiguration {
540462
return token !== undefined ? { succeeded: true, value: token } : { succeeded: false };
541463
}
542464

543-
/**
544-
* @private
545-
*/
546-
private isFromStreamingConnection(activity: Activity): boolean {
547-
return activity && activity.serviceUrl && !activity.serviceUrl.toLowerCase().startsWith('http');
548-
}
549-
550465
/**
551466
* @private
552467
*/
@@ -584,22 +499,6 @@ export class OAuthInput extends InputDialog implements OAuthInputConfiguration {
584499
return false;
585500
}
586501

587-
/**
588-
* @private
589-
*/
590-
private channelSupportsOAuthCard(channelId: string): boolean {
591-
switch (channelId) {
592-
case channels.msteams:
593-
case channels.cortana:
594-
case channels.skype:
595-
case channels.skypeforbusiness:
596-
return false;
597-
default:
598-
}
599-
600-
return true;
601-
}
602-
603502
/**
604503
* @private
605504
*/
@@ -612,19 +511,6 @@ export class OAuthInput extends InputDialog implements OAuthInputConfiguration {
612511
},
613512
});
614513
}
615-
616-
/**
617-
* @private
618-
*/
619-
private channelRequiresSignInLink(channelId: string): boolean {
620-
switch (channelId) {
621-
case Channels.Msteams:
622-
return true;
623-
default:
624-
}
625-
626-
return false;
627-
}
628514
}
629515

630516
/**

0 commit comments

Comments
 (0)