Skip to content
This repository was archived by the owner on Jan 5, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -60,7 +60,7 @@ public SendHandoffActivity([CallerFilePath] string callerPath = "", [CallerLineN
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
var handoffContext = HandoffContext?.GetValue(dc.State);
var context = (JObject)handoffContext;
var context = JObject.FromObject(handoffContext);
var contextResult = ((ValueExpression)context).EvaluateExpression(dc.State);
var transcript = Transcript?.GetValue(dc.State);
var eventActivity = EventFactory.CreateHandoffInitiation(dc.Context, contextResult, transcript);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,11 @@ private async Task<ClaimsPrincipal> ValidateTokenAsync(string jwtToken, string c

return principal;
}
catch (SecurityTokenExpiredException err)
{
Trace.TraceError(err.Message);
throw new UnauthorizedAccessException($"The token has expired");
}
catch (SecurityTokenSignatureKeyNotFoundException)
{
var keys = string.Join(", ", (config?.SigningKeys ?? Enumerable.Empty<SecurityKey>()).Select(t => t.KeyId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,26 @@ public async Task JwtTokenExtractor_WithValidCert_ShouldNotAllowCertSigningKey()
}
}

private static Task<ClaimsIdentity> BuildExtractorAndValidateToken(X509Certificate2 cert, TokenValidationParameters validationParameters = null)
[Fact]
[Trait("TestCategory", "WindowsOnly")]
public async Task JwtTokenExtractor_WithExpiredToken_ShouldThrowUnauthorizedAccessException()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var now = DateTimeOffset.UtcNow;
var cn = "test.cert.botframework.com";

// Create valid self-signed certificate
var cert = CreateSelfSignedCertificate(cn, from: now.AddDays(-10), to: now.AddDays(9));

// It will fail since the generated token is expired.
await Assert.ThrowsAnyAsync<UnauthorizedAccessException>(() => BuildExtractorAndValidateToken(cert, expires: now.Date.AddDays(-5), notBefore: now.Date.AddDays(-10)));

DeleteKeyContainer(cn);
}
}

private static Task<ClaimsIdentity> BuildExtractorAndValidateToken(X509Certificate2 cert, TokenValidationParameters validationParameters = null, DateTime? expires = null, DateTime? notBefore = null)
{
// Custom validation parameters that allow us to test the extractor logic
var tokenValidationParams = validationParameters ?? CreateTokenValidationParameters(cert);
Expand All @@ -103,17 +122,18 @@ private static Task<ClaimsIdentity> BuildExtractorAndValidateToken(X509Certifica
"https://login.botframework.com/v1/.well-known/openidconfiguration",
AuthenticationConstants.AllowedSigningAlgorithms);

var token = CreateTokenForCertificate(cert);
var token = CreateTokenForCertificate(cert, expires, notBefore);

return tokenExtractor.GetIdentityAsync($"Bearer {token}", "test");
}

private static string CreateTokenForCertificate(X509Certificate2 cert)
private static string CreateTokenForCertificate(X509Certificate2 cert, DateTime? expires = null, DateTime? notBefore = null)
{
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Expires = DateTime.UtcNow.AddMinutes(5),
Expires = expires ?? DateTime.UtcNow.AddMinutes(5),
NotBefore = notBefore,
SigningCredentials = new SigningCredentials(new X509SecurityKey(cert), SecurityAlgorithms.RsaSha256Signature)
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,36 @@ public async Task CloudAdapterCreateConversation()
Assert.Equal(expectedChannelId, actualChannelId);
}

[Fact]
public async Task ExpiredTokenShouldThrowUnauthorizedAccessException()
{
// Arrange
var headerDictionaryMock = new Mock<IHeaderDictionary>();

// Expired token with removed AppID
var token = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjJaUXBKM1VwYmpBWVhZR2FYRUpsOGxWMFRPSSIsImtpZCI6IjJaUXBKM1VwYmpBWVhZR2FYRUpsOGxWMFRPSSJ9.eyJhdWQiOiJodHRwczovL2FwaS5ib3RmcmFtZXdvcmsuY29tIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZDZkNDk0MjAtZjM5Yi00ZGY3LWExZGMtZDU5YTkzNTg3MWRiLyIsImlhdCI6MTY3MDM1MDQxNSwibmJmIjoxNjcwMzUwNDE1LCJleHAiOjE2NzA0MzcxMTUsImFpbyI6IkUyWmdZTkJONEpWZmxlOTJUc2wxYjhtOHBjOWpBQT09IiwiYXBwaWQiOiI5ZGRmM2QwZS02ZDRlLTQ2MWEtYjM4Yi0zMTYzZWQ3Yjg1NmIiLCJhcHBpZGFjciI6IjEiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9kNmQ0OTQyMC1mMzliLTRkZjctYTFkYy1kNTlhOTM1ODcxZGIvIiwicmgiOiIwLkFXNEFJSlRVMXB2ejkwMmgzTldhazFoeDIwSXpMWTBwejFsSmxYY09EcS05RnJ4dUFBQS4iLCJ0aWQiOiJkNmQ0OTQyMC1mMzliLTRkZjctYTFkYy1kNTlhOTM1ODcxZGIiLCJ1dGkiOiJIWDlncld2bU1rMlhESTRkS3BHSEFBIiwidmVyIjoiMS4wIn0.PBLuja5sCcDfFjweoy-VucvbfHEyEcs1GyqXjekzBqgvK-mSc1UrEfqr5834qY6dLNsXVIMJzMFuH6WyPbnAfIfRcabdiVSOAl8N8e9Tex6vHfPi4h4P2F96VkXU80EtZX4QMjsJMDJ5eXbJlIDEAxXoJbAdHqgy-lHcVBx8XK7toJ_W7vSsFhis3C4CPCHI1cf1WuHVwfFXBiNwsOzj9cnRUKpea6UELV89q4C0L6aeSNdWYXehZmgq-wlo2wIaGgQ7rOXx4MlIrc83LBzMMc6TWvBJecK6O8pJWLe6BTwOltBI8Tmo2hWnY1OnsbOhbSSlfwLaZqKI7QpA50_2GQ";

headerDictionaryMock.Setup(h => h[It.Is<string>(v => v == "Authorization")]).Returns<string>((_) => token);

var httpRequestMock = new Mock<HttpRequest>();
httpRequestMock.Setup(r => r.Method).Returns(HttpMethods.Post);
httpRequestMock.Setup(r => r.Body).Returns(CreateInvokeActivityStream());
httpRequestMock.Setup(r => r.Headers).Returns(headerDictionaryMock.Object);

var response = new MemoryStream();
var httpResponseMock = new Mock<HttpResponse>().SetupAllProperties();
httpResponseMock.Setup(r => r.Body).Returns(response);

var bot = new InvokeResponseBot();

// Act
var adapter = new CloudAdapter();
await adapter.ProcessAsync(httpRequestMock.Object, httpResponseMock.Object, bot);

// Assert
Assert.Equal((int)HttpStatusCode.Unauthorized, httpResponseMock.Object.StatusCode);
}

private static Stream CreateMessageActivityStream(string userId, string channelId, string conversationId, string recipient, string relatesToActivityId)
{
return CreateStream(new Activity
Expand Down Expand Up @@ -978,11 +1008,11 @@ private class ConnectorFactoryBot : IBot
public IIdentity Identity { get; private set; }

public IConnectorClient ConnectorClient { get; private set; }

public UserTokenClient UserTokenClient { get; private set; }

public BotCallbackHandler BotCallbackHandler { get; private set; }

public string OAuthScope { get; private set; }

public AuthenticationHeaderValue Authorization { get; private set; }
Expand Down Expand Up @@ -1036,9 +1066,9 @@ private class TestContentStream : IContentStream
public Guid Id { get; set; }

public string ContentType { get; set; }

public int? Length { get; set; }

public Stream Stream { get; set; }
}

Expand Down