-
Notifications
You must be signed in to change notification settings - Fork 1k
Adding unit tests to #278 #284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9ec821d
be9e2dd
56a4e7c
feb368c
2dc7b1f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApp-OpenIDConnect-DotNet | |
EndProject | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Web", "..\..\Microsoft.Identity.Web\Microsoft.Identity.Web.csproj", "{E0CEF26A-6CE6-4505-851B-6580D5564752}" | ||
EndProject | ||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CD0BB564-6C1E-4FCE-B9AB-7C637FDEE569}" | ||
ProjectSection(SolutionItems) = preProject | ||
.editorconfig = .editorconfig | ||
EndProjectSection | ||
EndProject | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Web.Test", "..\..\Microsoft.Identity.Web.Test\Microsoft.Identity.Web.Test.csproj", "{8CCEAE2A-BDF6-470C-B6DE-7FC81A74DBD7}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding the unit tests assembly to the solution |
||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
|
@@ -21,6 +28,10 @@ Global | |
{E0CEF26A-6CE6-4505-851B-6580D5564752}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{E0CEF26A-6CE6-4505-851B-6580D5564752}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{E0CEF26A-6CE6-4505-851B-6580D5564752}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{8CCEAE2A-BDF6-470C-B6DE-7FC81A74DBD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{8CCEAE2A-BDF6-470C-B6DE-7FC81A74DBD7}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{8CCEAE2A-BDF6-470C-B6DE-7FC81A74DBD7}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{8CCEAE2A-BDF6-470C-B6DE-7FC81A74DBD7}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,7 @@ | |
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.Graph" Version="1.14.0" /> | ||
<PackageReference Include="Microsoft.Graph" Version="1.21.0" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updating to the latest Graph SDK |
||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using Microsoft.AspNetCore.Authentication.JwtBearer; | ||
using System; | ||
jmprieur marked this conversation as resolved.
Show resolved
Hide resolved
|
||
using System.Collections.Generic; | ||
using System.Text; | ||
using Xunit; | ||
using System.Linq; | ||
|
||
namespace Microsoft.Identity.Web.Test | ||
{ | ||
public class WebApiServiceCollectionExtensionsTests | ||
{ | ||
[Fact] | ||
public void TestAuthority() | ||
{ | ||
// Arrange | ||
JwtBearerOptions options = new JwtBearerOptions(); | ||
jmprieur marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Act and Assert | ||
options.Authority = "https://login.microsoftonline.com/common"; | ||
WebApiServiceCollectionExtensions.EnsureAuthorityIsV2_0(options); | ||
Assert.Equal("https://login.microsoftonline.com/common/v2.0", options.Authority); | ||
|
||
options.Authority = "https://login.microsoftonline.us/organizations"; | ||
WebApiServiceCollectionExtensions.EnsureAuthorityIsV2_0(options); | ||
Assert.Equal("https://login.microsoftonline.us/organizations/v2.0", options.Authority); | ||
|
||
options.Authority = "https://login.microsoftonline.com/common/"; | ||
WebApiServiceCollectionExtensions.EnsureAuthorityIsV2_0(options); | ||
Assert.Equal("https://login.microsoftonline.com/common/v2.0", options.Authority); | ||
|
||
options.Authority = "https://login.microsoftonline.com/common/v2.0"; | ||
WebApiServiceCollectionExtensions.EnsureAuthorityIsV2_0(options); | ||
Assert.Equal("https://login.microsoftonline.com/common/v2.0", options.Authority); | ||
|
||
|
||
options.Authority = "https://login.microsoftonline.com/common/v2.0"; | ||
WebApiServiceCollectionExtensions.EnsureAuthorityIsV2_0(options); | ||
Assert.Equal("https://login.microsoftonline.com/common/v2.0", options.Authority); | ||
|
||
} | ||
|
||
[Fact] | ||
public void TestAudience() | ||
{ | ||
JwtBearerOptions options = new JwtBearerOptions(); | ||
|
||
// Act and Assert | ||
options.Audience = "https://localhost"; | ||
WebApiServiceCollectionExtensions.EnsureValidAudiencesContainsApiGuidIfGuidProvided(options); | ||
Assert.True(options.TokenValidationParameters.ValidAudiences.Count() == 1); | ||
Assert.True(options.TokenValidationParameters.ValidAudiences.First() == "https://localhost"); | ||
|
||
options.Audience = "api://1EE5A092-0DFD-42B6-88E5-C517C0141321"; | ||
WebApiServiceCollectionExtensions.EnsureValidAudiencesContainsApiGuidIfGuidProvided(options); | ||
Assert.True(options.TokenValidationParameters.ValidAudiences.Count() == 1); | ||
Assert.True(options.TokenValidationParameters.ValidAudiences.First() == "api://1EE5A092-0DFD-42B6-88E5-C517C0141321"); | ||
|
||
options.Audience = "1EE5A092-0DFD-42B6-88E5-C517C0141321"; | ||
WebApiServiceCollectionExtensions.EnsureValidAudiencesContainsApiGuidIfGuidProvided(options); | ||
Assert.True(options.TokenValidationParameters.ValidAudiences.Count() == 2); | ||
Assert.Contains("api://1EE5A092-0DFD-42B6-88E5-C517C0141321", options.TokenValidationParameters.ValidAudiences); | ||
Assert.Contains("1EE5A092-0DFD-42B6-88E5-C517C0141321", options.TokenValidationParameters.ValidAudiences); | ||
|
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,17 +53,11 @@ public static IServiceCollection AddProtectedWebApi( | |
configuration.Bind(configSectionName, options); | ||
|
||
// This is an Microsoft identity platform Web API | ||
var authority = options.Authority.Trim().TrimEnd('/'); | ||
if (!authority.EndsWith("v2.0")) | ||
authority += "/v2.0"; | ||
options.Authority = authority; | ||
EnsureAuthorityIsV2_0(options); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Refactoring for testability |
||
|
||
// The valid audience could be given as Client Id or as Uri. If it does not start with 'api://', this variant is added to the list of valid audiences. | ||
var validAudiences = new List<string> { options.Audience }; | ||
if (!options.Audience.StartsWith("api://", StringComparison.OrdinalIgnoreCase)) | ||
validAudiences.Add($"api://{options.Audience}"); | ||
|
||
options.TokenValidationParameters.ValidAudiences = validAudiences; | ||
// The valid audience could be given as Client Id or as Uri. | ||
// If it does not start with 'api://', this variant is added to the list of valid audiences. | ||
EnsureValidAudiencesContainsApiGuidIfGuidProvided(options); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Refactoring for testability |
||
|
||
// Instead of using the default validation (validating against a single tenant, as we do in line of business apps), | ||
// we inject our own multi-tenant validation logic (which even accepts both v1.0 and v2.0 tokens) | ||
|
@@ -80,17 +74,17 @@ public static IServiceCollection AddProtectedWebApi( | |
options.Events = new JwtBearerEvents(); | ||
|
||
options.Events.OnTokenValidated = async context => | ||
{ | ||
// This check is required to ensure that the Web API only accepts tokens from tenants where it has been consented and provisioned. | ||
if (!context.Principal.Claims.Any(x => x.Type == ClaimConstants.Scope) | ||
&& !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Scp) | ||
&& !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Roles)) | ||
{ | ||
throw new UnauthorizedAccessException("Neither scope or roles claim was found in the bearer token."); | ||
} | ||
|
||
await Task.FromResult(0); | ||
}; | ||
{ | ||
// This check is required to ensure that the Web API only accepts tokens from tenants where it has been consented and provisioned. | ||
if (!context.Principal.Claims.Any(x => x.Type == ClaimConstants.Scope) | ||
&& !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Scp) | ||
&& !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Roles)) | ||
{ | ||
throw new UnauthorizedAccessException("Neither scope or roles claim was found in the bearer token."); | ||
} | ||
|
||
await Task.FromResult(0); | ||
}; | ||
|
||
if (subscribeToJwtBearerMiddlewareDiagnosticsEvents) | ||
{ | ||
|
@@ -130,5 +124,37 @@ public static IServiceCollection AddProtectedApiCallsWebApis( | |
|
||
return services; | ||
} | ||
|
||
/// <summary> | ||
/// Ensures that the authority is a v2.0 authority | ||
/// </summary> | ||
/// <param name="options">Jwt bearer options read from the config file | ||
/// or set by the developper, for which we want to ensure the authority | ||
/// is a v2.0 authority</param> | ||
internal static void EnsureAuthorityIsV2_0(JwtBearerOptions options) | ||
{ | ||
var authority = options.Authority.Trim().TrimEnd('/'); | ||
if (!authority.EndsWith("v2.0")) | ||
authority += "/v2.0"; | ||
options.Authority = authority; | ||
} | ||
|
||
|
||
/// <summary> | ||
/// Ensure that if the audience is a GUID, api://{audience} is also added | ||
/// as a valid audience (this is the default App ID URL in the app registration | ||
/// portal) | ||
/// </summary> | ||
/// <param name="options">Jwt bearer options for which to ensure that | ||
/// api://GUID is a valid audience</param> | ||
internal static void EnsureValidAudiencesContainsApiGuidIfGuidProvided(JwtBearerOptions options) | ||
{ | ||
var validAudiences = new List<string> { options.Audience }; | ||
if (!options.Audience.StartsWith("api://", StringComparison.OrdinalIgnoreCase) | ||
&& Guid.TryParse(options.Audience, out _)) | ||
validAudiences.Add($"api://{options.Audience}"); | ||
|
||
options.TokenValidationParameters.ValidAudiences = validAudiences; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was already present in the .gitignore