// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Bot.Schema;
using Microsoft.Graph.Models;
namespace PABot.Dialogs
{
///
/// Main dialog that handles the authentication and user interactions.
///
public class MainDialog : LogoutDialog
{
protected readonly ILogger _logger;
private const string FirstOAuthPrompt = "FirstOAuthPrompt";
private const string SecondOAuthPrompt = "SecondOAuthPrompt";
private readonly string _firstConnectionName;
private readonly string _secondConnectionName;
///
/// Initializes a new instance of the class.
///
/// The configuration.
/// The logger.
public MainDialog(IConfiguration configuration, ILogger logger)
: base(nameof(MainDialog), configuration["ConnectionName"] ?? "graph")
{
_logger = logger;
// Load connection names from configuration
_firstConnectionName = configuration["ConnectionName"] ?? "graph";
_secondConnectionName = configuration["SecondConnectionName"] ?? "graph-2";
_logger.LogInformation("Using OAuth connections: {FirstConnection} and {SecondConnection}",
_firstConnectionName, _secondConnectionName);
// Add first OAuth prompt
AddDialog(new OAuthPrompt(
FirstOAuthPrompt,
new OAuthPromptSettings
{
ConnectionName = _firstConnectionName,
Text = $"Please Sign In to the first connection ({_firstConnectionName})",
Title = $"Sign In - {_firstConnectionName}",
Timeout = 300000, // User has 5 minutes to login (1000 * 60 * 5)
EndOnInvalidMessage = true
}));
// Add second OAuth prompt
AddDialog(new OAuthPrompt(
SecondOAuthPrompt,
new OAuthPromptSettings
{
ConnectionName = _secondConnectionName,
Text = $"Please Sign In to the second connection ({_secondConnectionName})",
Title = $"Sign In - {_secondConnectionName}",
Timeout = 300000, // User has 5 minutes to login (1000 * 60 * 5)
EndOnInvalidMessage = true
}));
AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
PromptFirstConnectionAsync,
LoginFirstConnectionAsync,
PromptSecondConnectionAsync,
LoginSecondConnectionAsync,
DisplayTokenPhase1Async,
DisplayTokenPhase2Async,
}));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
///
/// Prompts the user to sign in to the first connection.
///
/// The waterfall step context.
/// The cancellation token.
/// A task representing the asynchronous operation.
private async Task PromptFirstConnectionAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
_logger.LogInformation("PromptFirstConnectionAsync() called.");
return await stepContext.BeginDialogAsync(FirstOAuthPrompt, null, cancellationToken);
}
///
/// Handles the first connection login step.
///
/// The waterfall step context.
/// The cancellation token.
/// A task representing the asynchronous operation.
private async Task LoginFirstConnectionAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
TokenResponse firstTokenResponse = (TokenResponse)stepContext.Result;
if (firstTokenResponse?.Token != null)
{
_logger.LogInformation("First connection ({ConnectionName}) authenticated successfully.", _firstConnectionName);
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"✓ First connection ({_firstConnectionName}) authenticated successfully!"), cancellationToken);
// Store the first token in step context values
stepContext.Values["FirstToken"] = firstTokenResponse;
// Continue to next step
return await stepContext.NextAsync(null, cancellationToken);
}
else
{
_logger.LogInformation("First connection ({ConnectionName}) authentication failed.", _firstConnectionName);
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"First connection ({_firstConnectionName}) login was not successful, please try again."), cancellationToken);
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
}
///
/// Prompts the user to sign in to the second connection.
///
/// The waterfall step context.
/// The cancellation token.
/// A task representing the asynchronous operation.
private async Task PromptSecondConnectionAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
_logger.LogInformation("PromptSecondConnectionAsync() called.");
return await stepContext.BeginDialogAsync(SecondOAuthPrompt, null, cancellationToken);
}
///
/// Handles the second connection login step and displays user information.
///
/// The waterfall step context.
/// The cancellation token.
/// A task representing the asynchronous operation.
private async Task LoginSecondConnectionAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
TokenResponse secondTokenResponse = (TokenResponse)stepContext.Result;
if (secondTokenResponse?.Token != null)
{
_logger.LogInformation("Second connection ({ConnectionName}) authenticated successfully.", _secondConnectionName);
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"✓ Second connection ({_secondConnectionName}) authenticated successfully!"), cancellationToken);
// Store the second token
stepContext.Values["SecondToken"] = secondTokenResponse;
// Retrieve the first token
TokenResponse firstTokenResponse = (TokenResponse)stepContext.Values["FirstToken"];
try
{
// Use the first token to get user information
SimpleGraphClient client = new(firstTokenResponse.Token);
User me = await client.GetMeAsync();
string title = !string.IsNullOrEmpty(me.JobTitle) ? me.JobTitle : "Unknown";
await stepContext.Context.SendActivityAsync($"You're logged in as {me.DisplayName} ({me.UserPrincipalName}); your job title is: {title}");
string photo = await client.GetPhotoAsync();
if (!string.IsNullOrEmpty(photo))
{
CardImage cardImage = new(photo);
ThumbnailCard card = new(images: new List { cardImage });
IMessageActivity reply = MessageFactory.Attachment(card.ToAttachment());
await stepContext.Context.SendActivityAsync(reply, cancellationToken);
}
else
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("Sorry! User doesn't have a profile picture to display."), cancellationToken);
}
return await stepContext.PromptAsync(
nameof(ConfirmPrompt),
new PromptOptions { Prompt = MessageFactory.Text("Would you like to view your tokens?") },
cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while processing your request.");
}
}
else
{
_logger.LogInformation("Second connection ({ConnectionName}) authentication failed.", _secondConnectionName);
}
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Second connection ({_secondConnectionName}) login was not successful, please try again."), cancellationToken);
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
///
/// Displays the tokens if the user confirms.
///
/// The waterfall step context.
/// The cancellation token.
/// A task representing the asynchronous operation.
private async Task DisplayTokenPhase1Async(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
_logger.LogInformation("DisplayTokenPhase1Async() method called.");
await stepContext.Context.SendActivityAsync(MessageFactory.Text("Thank you."), cancellationToken);
bool result = (bool)stepContext.Result;
if (result)
{
// Pass both tokens to the next step
return await stepContext.NextAsync(null, cancellationToken);
}
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
///
/// Displays both tokens to the user.
///
/// The waterfall step context.
/// The cancellation token.
/// A task representing the asynchronous operation.
private async Task DisplayTokenPhase2Async(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
_logger.LogInformation("DisplayTokenPhase2Async() method called.");
// Retrieve both tokens from step context
TokenResponse firstTokenResponse = (TokenResponse)stepContext.Values["FirstToken"];
TokenResponse secondTokenResponse = (TokenResponse)stepContext.Values["SecondToken"];
if (firstTokenResponse != null && secondTokenResponse != null)
{
string tokenMessage = $"Here are your tokens:\n\n" +
$"{_firstConnectionName} Connection Token:\n{firstTokenResponse.Token}\n\n" +
$"{_secondConnectionName} Connection Token:\n{secondTokenResponse.Token}";
await stepContext.Context.SendActivityAsync(MessageFactory.Text(tokenMessage), cancellationToken);
}
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
///
/// Override to handle logout from both OAuth connections.
///
protected override async Task OnBeginDialogAsync(
DialogContext innerDc,
object options,
CancellationToken cancellationToken = default)
{
DialogTurnResult? result = await InterruptAsync(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnBeginDialogAsync(innerDc, options, cancellationToken);
}
///
/// Override to handle logout from both OAuth connections.
///
protected override async Task OnContinueDialogAsync(
DialogContext innerDc,
CancellationToken cancellationToken = default)
{
DialogTurnResult? result = await InterruptAsync(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
///
/// Handles logout command by signing out from both OAuth connections.
///
private async Task InterruptAsync(
DialogContext innerDc,
CancellationToken cancellationToken = default)
{
if (innerDc.Context.Activity.Type == ActivityTypes.Message)
{
string text = innerDc.Context.Activity.Text.ToLowerInvariant();
// Allow logout anywhere in the command
if (text.Contains("logout"))
{
// The UserTokenClient encapsulates the authentication processes.
UserTokenClient userTokenClient = innerDc.Context.TurnState.Get();
// Sign out from both connections
await userTokenClient.SignOutUserAsync(
innerDc.Context.Activity.From.Id,
_firstConnectionName,
innerDc.Context.Activity.ChannelId,
cancellationToken).ConfigureAwait(false);
await userTokenClient.SignOutUserAsync(
innerDc.Context.Activity.From.Id,
_secondConnectionName,
innerDc.Context.Activity.ChannelId,
cancellationToken).ConfigureAwait(false);
_logger.LogInformation("User signed out from both connections: {FirstConnection} and {SecondConnection}",
_firstConnectionName, _secondConnectionName);
await innerDc.Context.SendActivityAsync(
MessageFactory.Text($"You have been signed out from both connections ({_firstConnectionName} and {_secondConnectionName})."),
cancellationToken);
return await innerDc.CancelAllDialogsAsync(cancellationToken);
}
}
return null;
}
}
}