Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ public override async Task<DialogTurnResult> ContinueDialogAsync(DialogContext d
// if the token fetch request times out, complete the prompt with no result.
return await dc.EndDialogAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
}
catch(ConsentRequiredException)
{
// noop for now
}

return EndOfTurn;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class AgentApplication : IAgent
private readonly ConcurrentQueue<TurnEventHandler> _beforeTurn;
private readonly ConcurrentQueue<TurnEventHandler> _afterTurn;
private readonly ConcurrentQueue<AgentApplicationTurnError> _turnErrorHandlers;

public List<IAgentExtension> RegisteredExtensions { get; private set; } = new List<IAgentExtension>();

/// <summary>
Expand Down Expand Up @@ -126,7 +126,7 @@ public AgentApplication AddRoute(RouteSelector selector, RouteHandler handler, b
AssertionHelpers.ThrowIfNull(handler, nameof(handler));

_routes.AddRoute(selector, handler, isInvokeRoute, rank, autoSignInHandlers);

return this;
}

Expand All @@ -141,7 +141,7 @@ public AgentApplication AddRoute(RouteSelector selector, RouteHandler handler, b
/// <returns>The application instance for chaining purposes.</returns>
public AgentApplication OnActivity(string type, RouteHandler handler, ushort rank = RouteRank.Unspecified, string[] autoSignInHandlers = null, bool isAgenticOnly = false)
{
AssertionHelpers.ThrowIfNullOrWhiteSpace(type,nameof(type));
AssertionHelpers.ThrowIfNullOrWhiteSpace(type, nameof(type));
AssertionHelpers.ThrowIfNull(handler, nameof(handler));
Task<bool> routeSelector(ITurnContext context, CancellationToken _) => Task.FromResult(context.Activity.IsType(type) && (!isAgenticOnly || AgenticAuthorization.IsAgenticRequest(context)));
AddRoute(routeSelector, handler, false, rank, autoSignInHandlers);
Expand Down Expand Up @@ -183,7 +183,8 @@ public AgentApplication OnActivity(RouteSelector routeSelector, RouteHandler han
var rs = routeSelector;
if (isAgenticOnly)
{
rs = new RouteSelector(async (turnContext, cancellationToken) => {
rs = new RouteSelector(async (turnContext, cancellationToken) =>
{
return AgenticAuthorization.IsAgenticRequest(turnContext) && await routeSelector(turnContext, cancellationToken);
});
}
Expand Down Expand Up @@ -717,7 +718,7 @@ public AgentApplication OnTurnError(AgentApplicationTurnError handler)
return this;
}

#endregion
#endregion

#region ShowTyping
/// <summary>
Expand Down Expand Up @@ -783,7 +784,7 @@ public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancel
if (Options.StartTypingTimer)
{
StartTypingTimer(turnContext);
};
}

// Handle @mentions
if (ActivityTypes.Message.Equals(turnContext.Activity.Type, StringComparison.OrdinalIgnoreCase))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Agents.Builder.Errors;
using Microsoft.Agents.Builder.State;
using Microsoft.Agents.Builder.UserAuth;
using System.Threading.Tasks;
using System.Threading;
using System;
using Microsoft.Agents.Core.Errors;
using Microsoft.Agents.Core.Models;
using Microsoft.Agents.Core.Serialization;
using Microsoft.Agents.Builder.Errors;
using System;
using System.Collections.Generic;
using Microsoft.Agents.Core.Errors;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Agents.Builder.App.UserAuth
{
Expand Down Expand Up @@ -105,16 +105,16 @@ public async Task<string> ExchangeTurnTokenAsync(ITurnContext turnContext, strin
if (_authTokens.TryGetValue(handlerName, out var token))
{
// An exchangeable token needs to be exchanged.
if (!turnContext.IsAgenticRequest())
{
if (!token.IsExchangeable)
{
var diff = token.Expiration - DateTimeOffset.UtcNow;
if (diff.HasValue && diff?.TotalMinutes >= 5)
{
return token.Token;
}
}
if (!turnContext.IsAgenticRequest())
{
if (!token.IsExchangeable)
{
var diff = token.Expiration - DateTimeOffset.UtcNow;
if (diff.HasValue && diff?.TotalMinutes >= 5)
{
return token.Token;
}
}
}


Expand Down Expand Up @@ -185,14 +185,35 @@ internal async Task<bool> StartOrContinueSignInUserAsync(ITurnContext turnContex
// Auth flow hasn't start yet.
activeFlowName ??= handlerName ?? DefaultHandlerName;

if (!flowContinuation)
{
// Bank the incoming Activity so it can be executed after sign in is complete.
signInState.ContinuationActivity = turnContext.Activity;
signInState.ActiveHandler = activeFlowName;

// Save state now to avoid potential race with overlapping requests.
await turnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false);
}

// Get token or start flow for specified flow.
SignInResponse response = await _dispatcher.SignUserInAsync(
turnContext,
activeFlowName,
forceSignIn: !flowContinuation,
exchangeConnection: signInState.RuntimeOBOConnectionName,
exchangeScopes: signInState.RuntimeOBOScopes,
cancellationToken: cancellationToken).ConfigureAwait(false);
turnContext,
activeFlowName,
forceSignIn: !flowContinuation,
exchangeConnection: signInState.RuntimeOBOConnectionName,
exchangeScopes: signInState.RuntimeOBOScopes,
cancellationToken: cancellationToken).ConfigureAwait(false);

if (response.Status == SignInStatus.Cancelled)
{
// This is only for safety in case of unexpected behaviors during the MS Teams sign-in process,
// e.g., user interrupts the flow by clicking the Consent Cancel button.
DeleteSignInState(turnState);
await _dispatcher.ResetStateAsync(turnContext, activeFlowName, cancellationToken).ConfigureAwait(false);
await turnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false);
// Return true since at this point we are in a continuation flow, an we need to start a new flow that is handled later.
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'an' to 'and'.

Suggested change
// Return true since at this point we are in a continuation flow, an we need to start a new flow that is handled later.
// Return true since at this point we are in a continuation flow, and we need to start a new flow that is handled later.

Copilot uses AI. Check for mistakes.
return true;
}

if (response.Status == SignInStatus.Duplicate)
{
Expand All @@ -201,15 +222,6 @@ internal async Task<bool> StartOrContinueSignInUserAsync(ITurnContext turnContex

if (response.Status == SignInStatus.Pending)
{
if (!flowContinuation)
{
// Bank the incoming Activity so it can be executed after sign in is complete.
signInState.ContinuationActivity = turnContext.Activity;
signInState.ActiveHandler = activeFlowName;

await turnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false);
}

// Flow started, pending user input
return false;
}
Expand All @@ -226,12 +238,14 @@ internal async Task<bool> StartOrContinueSignInUserAsync(ITurnContext turnContex
if (_userSignInFailureHandler != null)
{
await _userSignInFailureHandler(turnContext, turnState, activeFlowName, response, signInState.ContinuationActivity, cancellationToken).ConfigureAwait(false);
return false;
}
else
{
await turnContext.SendActivitiesAsync(
_options.SignInFailedMessage == null ? [MessageFactory.Text("SignIn Failed")] : _options.SignInFailedMessage(activeFlowName, response),
cancellationToken).ConfigureAwait(false);
}

await turnContext.SendActivitiesAsync(
_options.SignInFailedMessage == null ? [MessageFactory.Text("SignIn Failed")] : _options.SignInFailedMessage(activeFlowName, response),
cancellationToken).ConfigureAwait(false);
return false;
}

Expand All @@ -249,11 +263,12 @@ await turnContext.SendActivitiesAsync(
{
// Since we could be handling an Invoke in this turn, and Teams has expectation for Invoke response times,
// we need to continue the conversation in a different turn with the original Activity that triggered sign in.
// It is important to save state now so that the continuationActivity doesn't try to continue the flow again.
await turnState.SaveStateAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false);
await _app.Options.Adapter.ProcessProactiveAsync(
turnContext.Identity,
signInState.ContinuationActivity.ApplyConversationReference(turnContext.Activity.GetConversationReference(), isIncoming: true),
_app,
turnContext.Identity,
signInState.ContinuationActivity.ApplyConversationReference(turnContext.Activity.GetConversationReference(), isIncoming: true),
_app,
cancellationToken).ConfigureAwait(false);
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,23 @@ public enum SignInStatus
/// Sign-in not complete and requires user interaction
/// </summary>
Pending,

/// <summary>
/// Sign-in complete
/// </summary>
Complete,

/// <summary>
/// Error occurred during sign-in
/// </summary>
Error,

Duplicate
/// <summary>
/// Indicates that the operation is a duplicate and has already been processed.
/// </summary>
Duplicate,
/// <summary>
/// Indicates that the operation has been cancelled.
/// </summary>
/// <remarks>This status is typically used to represent an operation that was explicitly stopped
/// before completion, either by user action or programmatically.</remarks>
Cancelled,
}
}
Loading
Loading