Merge branch 'release' of github.com:aspnet/Security into release
This commit is contained in:
commit
9743b590ab
|
|
@ -2,5 +2,6 @@
|
|||
<configuration>
|
||||
<packageSources>
|
||||
<add key="NuGet.org" value="https://nuget.org/api/v2/" />
|
||||
<add key="AzureADNighty" value="http://www.myget.org/F/azureadwebstacknightly"/>
|
||||
</packageSources>
|
||||
</configuration>
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ ASP.NET Security
|
|||
|
||||
ASP.NET Security contains the security and authorization middlewares for ASP.NET vNext.
|
||||
|
||||
This project is part of ASP.NET vNext. You can find samples, documentation and getting started instructions for ASP.NET vNext at the [Home](https://github.com/aspnet/home) repo.
|
||||
This project is part of ASP.NET 5. You can find samples, documentation and getting started instructions for ASP.NET 5 at the [Home](https://github.com/aspnet/home) repo.
|
||||
|
|
|
|||
47
Security.sln
47
Security.sln
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.22013.1
|
||||
VisualStudioVersion = 14.0.22422.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9F-44F5-8131-EA5CAC053652}"
|
||||
EndProject
|
||||
|
|
@ -36,6 +36,12 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.O
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CookieSessionSample", "samples\CookieSessionSample\CookieSessionSample.kproj", "{19711880-46DA-4A26-9E0F-9B2E41D27651}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "OpenIdConnectSample", "samples\OpenIdConnectSample\OpenIdConnectSample.kproj", "{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.OAuthBearer", "src\Microsoft.AspNet.Security.OAuthBearer\Microsoft.AspNet.Security.OAuthBearer.kproj", "{2755BFE5-7421-4A31-A644-F817DF5CAA98}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.OpenIdConnect", "src\Microsoft.AspNet.Security.OpenIdConnect\Microsoft.AspNet.Security.OpenIdConnect.kproj", "{674D128E-83BB-481A-A9D9-6D47872E1FC8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -156,6 +162,42 @@ Global
|
|||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B}.Release|x86.Build.0 = Release|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98}.Release|x86.Build.0 = Release|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -172,5 +214,8 @@ Global
|
|||
{1FCF26C2-A3C7-4308-B698-4AFC3560BC0C} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||
{4A636011-68EE-4CE5-836D-EA8E13CF71E4} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
|
||||
{BEF0F5C3-EF4E-4649-9C49-D5E279A3CA2B} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
|
||||
{2755BFE5-7421-4A31-A644-F817DF5CAA98} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||
{674D128E-83BB-481A-A9D9-6D47872E1FC8} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ IF EXIST packages\KoreBuild goto run
|
|||
.nuget\NuGet.exe install KoreBuild -ExcludeVersion -o packages -nocache -pre
|
||||
.nuget\NuGet.exe install Sake -version 0.2 -o packages -ExcludeVersion
|
||||
|
||||
IF "%SKIP_KRE_INSTALL%"=="1" goto run
|
||||
CALL packages\KoreBuild\build\kvm upgrade -runtime CLR -x86
|
||||
CALL packages\KoreBuild\build\kvm install default -runtime CoreCLR -x86
|
||||
IF "%SKIP_DOTNET_INSTALL%"=="1" goto run
|
||||
CALL packages\KoreBuild\build\dotnetsdk upgrade -runtime CLR -x86
|
||||
CALL packages\KoreBuild\build\dotnetsdk install default -runtime CoreCLR -x86
|
||||
|
||||
:run
|
||||
CALL packages\KoreBuild\build\kvm use default -runtime CLR -x86
|
||||
CALL packages\KoreBuild\build\dotnetsdk use default -runtime CLR -x86
|
||||
packages\Sake\tools\Sake.exe -I packages\KoreBuild\build -f makefile.shade %*
|
||||
|
|
|
|||
4
build.sh
4
build.sh
|
|
@ -28,11 +28,11 @@ if test ! -d packages/KoreBuild; then
|
|||
fi
|
||||
|
||||
if ! type k > /dev/null 2>&1; then
|
||||
source packages/KoreBuild/build/kvm.sh
|
||||
source packages/KoreBuild/build/dotnetsdk.sh
|
||||
fi
|
||||
|
||||
if ! type k > /dev/null 2>&1; then
|
||||
kvm upgrade
|
||||
dotnetsdk upgrade
|
||||
fi
|
||||
|
||||
mono packages/Sake/tools/Sake.exe -I packages/KoreBuild/build -f makefile.shade "$@"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>bef0f5c3-ef4e-4649-9c49-d5e279a3ca2b</ProjectGuid>
|
||||
<RootNamespace>OpenIDConnectSample</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<AssemblyName>OpenIDConnectSample</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<DevelopmentServerPort>42023</DevelopmentServerPort>
|
||||
<CommandLineArguments />
|
||||
<DebugTarget>
|
||||
</DebugTarget>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<UserProperties project_1json__JSONSchema="http://www.asp.net/media/4878834/project.json" />
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.Security;
|
||||
using Microsoft.AspNet.Security.Cookies;
|
||||
using Microsoft.AspNet.Security.OpenIdConnect;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
|
||||
namespace OpenIdConnectSample
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseServices(services =>
|
||||
{
|
||||
services.AddDataProtection();
|
||||
services.Configure<ExternalAuthenticationOptions>(options =>
|
||||
{
|
||||
options.SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
app.UseCookieAuthentication(options =>
|
||||
{
|
||||
});
|
||||
|
||||
app.UseOpenIdConnectAuthentication(options =>
|
||||
{
|
||||
options.ClientId = "fe78e0b4-6fe7-47e6-812c-fb75cee266a4";
|
||||
options.Authority = "https://login.windows.net/cyrano.onmicrosoft.com";
|
||||
options.RedirectUri = "http://localhost:42023";
|
||||
});
|
||||
|
||||
app.Run(async context =>
|
||||
{
|
||||
if (context.User == null || !context.User.Identity.IsAuthenticated)
|
||||
{
|
||||
context.Response.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
|
||||
|
||||
context.Response.ContentType = "text/plain";
|
||||
await context.Response.WriteAsync("Hello First timer");
|
||||
return;
|
||||
}
|
||||
|
||||
context.Response.ContentType = "text/plain";
|
||||
await context.Response.WriteAsync("Hello Authenticated User");
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"Kestrel": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.Cookies": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.OpenIdConnect": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.WebListener": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": { },
|
||||
"aspnetcore50": { }
|
||||
},
|
||||
"commands": {
|
||||
"web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:12345",
|
||||
"kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5004"
|
||||
},
|
||||
"webroot": "wwwroot"
|
||||
}
|
||||
|
|
@ -146,7 +146,7 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
{
|
||||
Domain = Options.CookieDomain,
|
||||
HttpOnly = Options.CookieHttpOnly,
|
||||
Path = Options.CookiePath ?? "/",
|
||||
Path = Options.CookiePath ?? (RequestPathBase.HasValue ? RequestPathBase.ToString() : "/"),
|
||||
};
|
||||
if (Options.CookieSecure == CookieSecureOption.SameAsRequest)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
{
|
||||
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType;
|
||||
ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
|
||||
CookiePath = "/";
|
||||
ExpireTimeSpan = TimeSpan.FromDays(14);
|
||||
SlidingExpiration = true;
|
||||
CookieHttpOnly = true;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using System.Security.Claims;
|
|||
using System.Security.Principal;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.HttpFeature.Security;
|
||||
using Microsoft.AspNet.Http.Interfaces.Security;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.AspNet.Security.Notifications;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ using System.Security.Cryptography;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Core.Collections;
|
||||
using Microsoft.AspNet.Http.Extensions;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.Security.OAuth;
|
||||
using Microsoft.AspNet.WebUtilities;
|
||||
|
|
@ -39,7 +41,7 @@ namespace Microsoft.AspNet.Security.Facebook
|
|||
tokenResponse.EnsureSuccessStatusCode();
|
||||
string oauthTokenResponse = await tokenResponse.Content.ReadAsStringAsync();
|
||||
|
||||
IFormCollection form = FormHelpers.ParseForm(oauthTokenResponse);
|
||||
IFormCollection form = new FormCollection(FormReader.ReadForm(oauthTokenResponse));
|
||||
var response = new JObject();
|
||||
foreach (string key in form.Keys)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@
|
|||
},
|
||||
"frameworks": {
|
||||
"aspnet50": {},
|
||||
"aspnetcore50": {}
|
||||
"aspnetcore50": {
|
||||
"dependencies": {
|
||||
"System.Dynamic.Runtime": "4.0.0-beta-*",
|
||||
"System.ObjectModel": "4.0.10-beta-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies callback methods which the <see cref="OAuthBearerAuthenticationMiddleware"></see> invokes to enable developer control over the authentication process. />
|
||||
/// </summary>
|
||||
public interface IOAuthBearerAuthenticationNotifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked before the <see cref="System.Security.Claims.ClaimsIdentity"/> is created. Gives the application an
|
||||
/// opportunity to find the identity from a different location, adjust, or reject the token.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains the token string.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
Task RequestToken(OAuthRequestTokenContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Called each time a request identity has been validated by the middleware. By implementing this method the
|
||||
/// application may alter or reject the identity which has arrived with the request.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
Task ValidateIdentity(OAuthValidateIdentityContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Called each time a challenge is being sent to the client. By implementing this method the application
|
||||
/// may modify the challenge as needed.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains the default challenge.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
|
||||
Task ApplyChallenge(OAuthChallengeContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth bearer token middleware provider
|
||||
/// </summary>
|
||||
public class OAuthBearerAuthenticationNotifications : IOAuthBearerAuthenticationNotifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthBearerAuthenticationProvider"/> class
|
||||
/// </summary>
|
||||
public OAuthBearerAuthenticationNotifications()
|
||||
{
|
||||
OnRequestToken = context => Task.FromResult<object>(null);
|
||||
OnValidateIdentity = context => Task.FromResult<object>(null);
|
||||
OnApplyChallenge = context =>
|
||||
{
|
||||
context.HttpContext.Response.Headers.AppendValues("WWW-Authenticate", context.Challenge);
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles processing OAuth bearer token.
|
||||
/// </summary>
|
||||
public Func<OAuthRequestTokenContext, Task> OnRequestToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Handles validating the identity produced from an OAuth bearer token.
|
||||
/// </summary>
|
||||
public Func<OAuthValidateIdentityContext, Task> OnValidateIdentity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Handles applying the authentication challenge to the response message.
|
||||
/// </summary>
|
||||
public Func<OAuthChallengeContext, Task> OnApplyChallenge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Handles processing OAuth bearer token.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public virtual Task RequestToken(OAuthRequestTokenContext context)
|
||||
{
|
||||
return OnRequestToken(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles validating the identity produced from an OAuth bearer token.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public virtual Task ValidateIdentity(OAuthValidateIdentityContext context)
|
||||
{
|
||||
return OnValidateIdentity.Invoke(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles applying the authentication challenge to the response message.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public Task ApplyChallenge(OAuthChallengeContext context)
|
||||
{
|
||||
return OnApplyChallenge(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the authentication ticket data from an OAuth bearer token.
|
||||
/// </summary>
|
||||
public class OAuthValidateIdentityContext : BaseValidatingTicketContext<OAuthBearerAuthenticationOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthValidateIdentityContext"/> class
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="ticket"></param>
|
||||
public OAuthValidateIdentityContext(
|
||||
HttpContext context,
|
||||
OAuthBearerAuthenticationOptions options,
|
||||
AuthenticationTicket ticket) : base(context, options, ticket)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ using System.Net.Http.Headers;
|
|||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Extensions;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.AspNet.WebUtilities;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Security.DataHandler;
|
||||
using Microsoft.AspNet.Security.DataProtection;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
{
|
||||
|
|
@ -45,18 +45,22 @@ namespace Microsoft.AspNet.Security.OAuth
|
|||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "AuthenticationType"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Options.ClientId))
|
||||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientId"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Options.ClientSecret))
|
||||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "ClientSecret"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Options.AuthorizationEndpoint))
|
||||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "AuthorizationEndpoint"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Options.TokenEndpoint))
|
||||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "TokenEndpoint"));
|
||||
|
|
|
|||
|
|
@ -1,126 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.Framework.Logging;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
{
|
||||
internal class OAuthBearerAuthenticationHandler : AuthenticationHandler<OAuthBearerAuthenticationOptions>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly string _challenge;
|
||||
|
||||
public OAuthBearerAuthenticationHandler(ILogger logger, string challenge)
|
||||
{
|
||||
_logger = logger;
|
||||
_challenge = challenge;
|
||||
}
|
||||
|
||||
protected override AuthenticationTicket AuthenticateCore()
|
||||
{
|
||||
return AuthenticateCoreAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Find token in default location
|
||||
string requestToken = null;
|
||||
string authorization = Request.Headers.Get("Authorization");
|
||||
if (!string.IsNullOrEmpty(authorization))
|
||||
{
|
||||
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
requestToken = authorization.Substring("Bearer ".Length).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Give application opportunity to find from a different location, adjust, or reject token
|
||||
var requestTokenContext = new OAuthRequestTokenContext(Context, requestToken);
|
||||
await Options.Notifications.RequestToken(requestTokenContext);
|
||||
|
||||
// If no token found, no further work possible
|
||||
if (string.IsNullOrEmpty(requestTokenContext.Token))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Call provider to process the token into data
|
||||
var tokenReceiveContext = new AuthenticationTokenReceiveContext(
|
||||
Context,
|
||||
Options.AccessTokenFormat,
|
||||
requestTokenContext.Token);
|
||||
|
||||
await Options.AccessTokenProvider.ReceiveAsync(tokenReceiveContext);
|
||||
if (tokenReceiveContext.Ticket == null)
|
||||
{
|
||||
tokenReceiveContext.DeserializeTicket(tokenReceiveContext.Token);
|
||||
}
|
||||
|
||||
AuthenticationTicket ticket = tokenReceiveContext.Ticket;
|
||||
if (ticket == null)
|
||||
{
|
||||
_logger.WriteWarning("invalid bearer token received");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate expiration time if present
|
||||
DateTimeOffset currentUtc = Options.SystemClock.UtcNow;
|
||||
|
||||
if (ticket.Properties.ExpiresUtc.HasValue &&
|
||||
ticket.Properties.ExpiresUtc.Value < currentUtc)
|
||||
{
|
||||
_logger.WriteWarning("expired bearer token received");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Give application final opportunity to override results
|
||||
var context = new OAuthValidateIdentityContext(Context, Options, ticket);
|
||||
if (ticket != null &&
|
||||
ticket.Identity != null &&
|
||||
ticket.Identity.IsAuthenticated)
|
||||
{
|
||||
// bearer token with identity starts validated
|
||||
context.Validated();
|
||||
}
|
||||
|
||||
await Options.Notifications.ValidateIdentity(context);
|
||||
if (!context.IsValidated)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// resulting identity values go back to caller
|
||||
return context.Ticket;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.WriteError("Authentication failed", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyResponseChallenge()
|
||||
{
|
||||
if (Response.StatusCode != 401)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ChallengeContext != null)
|
||||
{
|
||||
OAuthChallengeContext challengeContext = new OAuthChallengeContext(Context, _challenge);
|
||||
Options.Notifications.ApplyChallenge(challengeContext);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyResponseGrant()
|
||||
{
|
||||
// N/A
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Security.DataHandler;
|
||||
using Microsoft.AspNet.Security.DataProtection;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Bearer authentication middleware component which is added to an HTTP pipeline. This class is not
|
||||
/// created by application code directly, instead it is added by calling the the IAppBuilder UseOAuthBearerAuthentication
|
||||
/// extension method.
|
||||
/// </summary>
|
||||
public class OAuthBearerAuthenticationMiddleware : AuthenticationMiddleware<OAuthBearerAuthenticationOptions>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly string _challenge;
|
||||
|
||||
/// <summary>
|
||||
/// Bearer authentication component which is added to an HTTP pipeline. This constructor is not
|
||||
/// called by application code directly, instead it is added by calling the the IAppBuilder UseOAuthBearerAuthentication
|
||||
/// extension method.
|
||||
/// </summary>
|
||||
public OAuthBearerAuthenticationMiddleware(
|
||||
RequestDelegate next,
|
||||
IServiceProvider services,
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<OAuthBearerAuthenticationOptions> options,
|
||||
ConfigureOptions<OAuthBearerAuthenticationOptions> configureOptions)
|
||||
: base(next, services, options, configureOptions)
|
||||
{
|
||||
_logger = loggerFactory.Create<OAuthBearerAuthenticationMiddleware>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Options.Challenge))
|
||||
{
|
||||
_challenge = Options.Challenge;
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(Options.Realm))
|
||||
{
|
||||
_challenge = "Bearer";
|
||||
}
|
||||
else
|
||||
{
|
||||
_challenge = "Bearer realm=\"" + Options.Realm + "\"";
|
||||
}
|
||||
|
||||
if (Options.Notifications == null)
|
||||
{
|
||||
Options.Notifications = new OAuthBearerAuthenticationNotifications();
|
||||
}
|
||||
|
||||
if (Options.AccessTokenFormat == null)
|
||||
{
|
||||
IDataProtector dataProtector = dataProtectionProvider.CreateDataProtector(
|
||||
this.GetType().FullName, Options.AuthenticationType, "v1");
|
||||
Options.AccessTokenFormat = new TicketDataFormat(dataProtector);
|
||||
}
|
||||
|
||||
if (Options.AccessTokenProvider == null)
|
||||
{
|
||||
Options.AccessTokenProvider = new AuthenticationTokenProvider();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the AuthenticationMiddleware base class to create a per-request handler.
|
||||
/// </summary>
|
||||
/// <returns>A new instance of the request handler</returns>
|
||||
protected override AuthenticationHandler<OAuthBearerAuthenticationOptions> CreateHandler()
|
||||
{
|
||||
return new OAuthBearerAuthenticationHandler(_logger, _challenge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Options class provides information needed to control Bearer Authentication middleware behavior
|
||||
/// </summary>
|
||||
public class OAuthBearerAuthenticationOptions : AuthenticationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an instance of bearer authentication options with default values.
|
||||
/// </summary>
|
||||
public OAuthBearerAuthenticationOptions() : base()
|
||||
{
|
||||
SystemClock = new SystemClock();
|
||||
AuthenticationType = OAuthBearerAuthenticationDefaults.AuthenticationType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines what realm value is included when the bearer middleware adds a response header to an unauthorized request.
|
||||
/// If not assigned, the response header does not have a realm.
|
||||
/// </summary>
|
||||
public string Realm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the full challenge to send to the client, and should start with "Bearer". If a challenge is provided then the
|
||||
/// Realm property is ignored. If no challenge is specified then one is created using "Bearer" and the value of the Realm
|
||||
/// property.
|
||||
/// </summary>
|
||||
public string Challenge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The object provided by the application to process events raised by the bearer authentication middleware.
|
||||
/// The application may implement the interface fully, or it may create an instance of OAuthBearerAuthenticationProvider
|
||||
/// and assign delegates only to the events it wants to process.
|
||||
/// </summary>
|
||||
public IOAuthBearerAuthenticationNotifications Notifications { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The data format used to un-protect the information contained in the access token.
|
||||
/// If not provided by the application the default data protection provider depends on the host server.
|
||||
/// The SystemWeb host on IIS will use ASP.NET machine key data protection, and HttpListener and other self-hosted
|
||||
/// servers will use DPAPI data protection. If a different access token
|
||||
/// provider or format is assigned, a compatible instance must be assigned to the OAuthAuthorizationServerOptions.AccessTokenProvider
|
||||
/// and OAuthAuthorizationServerOptions.AccessTokenFormat of the authorization server.
|
||||
/// </summary>
|
||||
public ISecureDataFormat<AuthenticationTicket> AccessTokenFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Receives the bearer token the client application will be providing to web application. If not provided the token
|
||||
/// produced on the server's default data protection by using the AccessTokenFormat. If a different access token
|
||||
/// provider or format is assigned, a compatible instance must be assigned to the OAuthAuthorizationServerOptions.AccessTokenProvider
|
||||
/// and OAuthAuthorizationServerOptions.AccessTokenFormat of the authorization server.
|
||||
/// </summary>
|
||||
public IAuthenticationTokenProvider AccessTokenProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to know what the current clock time is when calculating or validating token expiration. When not assigned default is based on
|
||||
/// DateTimeOffset.UtcNow. This is typically needed only for unit testing.
|
||||
/// </summary>
|
||||
public ISystemClock SystemClock { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,8 @@
|
|||
"version": "1.0.0-*",
|
||||
"description": "ASP.NET 5 middleware that enables an application to support any standard OAuth 2.0 authentication workflow.",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.DataProtection": "1.0.0-*",
|
||||
"Microsoft.AspNet.WebUtilities": "1.0.0-*",
|
||||
"Microsoft.Framework.Logging": "1.0.0-*",
|
||||
"Newtonsoft.Json": "6.0.6"
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": {
|
||||
|
|
@ -18,28 +14,7 @@
|
|||
},
|
||||
"aspnetcore50": {
|
||||
"dependencies": {
|
||||
"System.Collections": "4.0.10-beta-*",
|
||||
"System.ComponentModel": "4.0.0-beta-*",
|
||||
"System.Console": "4.0.0-beta-*",
|
||||
"System.Diagnostics.Debug": "4.0.10-beta-*",
|
||||
"System.Diagnostics.Tools": "4.0.0-beta-*",
|
||||
"System.Dynamic.Runtime": "4.0.0-beta-*",
|
||||
"System.Globalization": "4.0.10-beta-*",
|
||||
"System.IO": "4.0.10-beta-*",
|
||||
"System.Linq": "4.0.0-beta-*",
|
||||
"System.Net.Http.WinHttpHandler": "4.0.0-beta-*",
|
||||
"System.ObjectModel": "4.0.10-beta-*",
|
||||
"System.Reflection": "4.0.10-beta-*",
|
||||
"System.Resources.ResourceManager": "4.0.0-beta-*",
|
||||
"System.Runtime": "4.0.20-beta-*",
|
||||
"System.Runtime.Extensions": "4.0.10-beta-*",
|
||||
"System.Runtime.InteropServices": "4.0.20-beta-*",
|
||||
"System.Security.Claims": "4.0.0-beta-*",
|
||||
"System.Security.Cryptography.Hashing.Algorithms": "4.0.0-beta-*",
|
||||
"System.Security.Principal": "4.0.0-beta-*",
|
||||
"System.Threading": "4.0.0-beta-*",
|
||||
"System.Threading.Tasks": "4.0.10-beta-*",
|
||||
"System.Net.Http": "4.0.0-beta-*"
|
||||
"System.Net.Http.WinHttpHandler": "4.0.0-beta-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>2755BFE5-7421-4A31-A644-F817DF5CAA98</ProjectGuid>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<UserProperties project_1json__JSONSchema="http://www.asp.net/media/4878834/project.json" />
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
|
||||
internal sealed class NotNullAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Security.Notifications;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
public class AuthenticationChallengeNotification<TOptions> : BaseNotification<TOptions>
|
||||
{
|
||||
public AuthenticationChallengeNotification(HttpContext context, TOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Security.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies events which the <see cref="OAuthBearerAuthenticationMiddleware"></see> invokes to enable developer control over the authentication process. />
|
||||
/// </summary>
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth bearer token middleware provider
|
||||
/// </summary>
|
||||
public class OAuthBearerAuthenticationNotifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthBearerAuthenticationProvider"/> class
|
||||
/// </summary>
|
||||
public OAuthBearerAuthenticationNotifications()
|
||||
{
|
||||
ApplyChallenge = notification => { notification.HttpContext.Response.Headers.AppendValues("WWW-Authenticate", notification.Options.Challenge); return Task.FromResult(0); };
|
||||
AuthenticationFailed = notification => Task.FromResult(0);
|
||||
MessageReceived = notification => Task.FromResult(0);
|
||||
SecurityTokenReceived = notification => Task.FromResult(0);
|
||||
SecurityTokenValidated = notification => Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed.
|
||||
/// </summary>
|
||||
public Func<AuthenticationFailedNotification<HttpContext, OAuthBearerAuthenticationOptions>, Task> AuthenticationFailed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a protocol message is first received.
|
||||
/// </summary>
|
||||
public Func<MessageReceivedNotification<HttpContext, OAuthBearerAuthenticationOptions>, Task> MessageReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked with the security token that has been extracted from the protocol message.
|
||||
/// </summary>
|
||||
public Func<SecurityTokenReceivedNotification<HttpContext, OAuthBearerAuthenticationOptions>, Task> SecurityTokenReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked after the security token has passed validation and a ClaimsIdentity has been generated.
|
||||
/// </summary>
|
||||
public Func<SecurityTokenValidatedNotification<HttpContext, OAuthBearerAuthenticationOptions>, Task> SecurityTokenValidated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked to apply a challenge sent back to the caller.
|
||||
/// </summary>
|
||||
public Func<AuthenticationChallengeNotification<OAuthBearerAuthenticationOptions>, Task> ApplyChallenge { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuth
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
/// <summary>
|
||||
/// Default values used by authorization server and bearer authentication.
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Security.OAuth;
|
||||
using Microsoft.AspNet.Security.OAuthBearer;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.AspNet.Security.Notifications;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
public class OAuthBearerAuthenticationHandler : AuthenticationHandler<OAuthBearerAuthenticationOptions>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private OpenIdConnectConfiguration _configuration;
|
||||
|
||||
public OAuthBearerAuthenticationHandler(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override AuthenticationTicket AuthenticateCore()
|
||||
{
|
||||
return AuthenticateCoreAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches the 'Authorization' header for a 'Bearer' token. If the 'Bearer' token is found, it is validated using <see cref="TokenValidationParameters"/> set in the options.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
|
||||
{
|
||||
string token = null;
|
||||
try
|
||||
{
|
||||
// Give application opportunity to find from a different location, adjust, or reject token
|
||||
var messageReceivedNotification =
|
||||
new MessageReceivedNotification<HttpContext, OAuthBearerAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = Context,
|
||||
};
|
||||
|
||||
// notification can set the token
|
||||
await Options.Notifications.MessageReceived(messageReceivedNotification);
|
||||
if (messageReceivedNotification.HandledResponse)
|
||||
{
|
||||
return messageReceivedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (messageReceivedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// If application retrieved token from somewhere else, use that.
|
||||
token = messageReceivedNotification.Token;
|
||||
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
string authorization = Request.Headers.Get("Authorization");
|
||||
|
||||
// If no authorization header found, nothing to process further
|
||||
if (string.IsNullOrEmpty(authorization))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
token = authorization.Substring("Bearer ".Length).Trim();
|
||||
}
|
||||
|
||||
// If no token found, no further work possible
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// notify user token was received
|
||||
var securityTokenReceivedNotification =
|
||||
new SecurityTokenReceivedNotification<HttpContext, OAuthBearerAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = Context,
|
||||
SecurityToken = token,
|
||||
};
|
||||
|
||||
await Options.Notifications.SecurityTokenReceived(securityTokenReceivedNotification);
|
||||
if (securityTokenReceivedNotification.HandledResponse)
|
||||
{
|
||||
return securityTokenReceivedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (securityTokenReceivedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_configuration == null && Options.ConfigurationManager != null)
|
||||
{
|
||||
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
|
||||
}
|
||||
|
||||
var validationParameters = Options.TokenValidationParameters.Clone();
|
||||
if (_configuration != null)
|
||||
{
|
||||
if (validationParameters.ValidIssuer == null && !string.IsNullOrWhiteSpace(_configuration.Issuer))
|
||||
{
|
||||
validationParameters.ValidIssuer = _configuration.Issuer;
|
||||
}
|
||||
else
|
||||
{
|
||||
IEnumerable<string> issuers = new[] { _configuration.Issuer };
|
||||
validationParameters.ValidIssuers = (validationParameters.ValidIssuers == null ? issuers : validationParameters.ValidIssuers.Concat(issuers));
|
||||
}
|
||||
|
||||
validationParameters.IssuerSigningKeys = (validationParameters.IssuerSigningKeys == null ? _configuration.SigningKeys : validationParameters.IssuerSigningKeys.Concat(_configuration.SigningKeys));
|
||||
}
|
||||
|
||||
SecurityToken validatedToken;
|
||||
foreach (var validator in Options.SecurityTokenValidators)
|
||||
{
|
||||
if (validator.CanReadToken(token))
|
||||
{
|
||||
ClaimsPrincipal principal = validator.ValidateToken(token, validationParameters, out validatedToken);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), Options.AuthenticationType);
|
||||
var securityTokenValidatedNotification = new SecurityTokenValidatedNotification<HttpContext, OAuthBearerAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = Context,
|
||||
AuthenticationTicket = ticket
|
||||
};
|
||||
|
||||
if (securityTokenReceivedNotification.HandledResponse)
|
||||
{
|
||||
return securityTokenValidatedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (securityTokenReceivedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ticket;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("No SecurityTokenValidator available for token: " + token ?? "null");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.WriteError("Exception occurred while processing message", ex);
|
||||
|
||||
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the notification.
|
||||
if (Options.RefreshOnIssuerKeyNotFound && ex.GetType().Equals(typeof(SecurityTokenSignatureKeyNotFoundException)))
|
||||
{
|
||||
Options.ConfigurationManager.RequestRefresh();
|
||||
}
|
||||
|
||||
var authenticationFailedNotification =
|
||||
new AuthenticationFailedNotification<HttpContext, OAuthBearerAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = Context,
|
||||
Exception = ex
|
||||
};
|
||||
|
||||
await Options.Notifications.AuthenticationFailed(authenticationFailedNotification);
|
||||
if (authenticationFailedNotification.HandledResponse)
|
||||
{
|
||||
return authenticationFailedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (authenticationFailedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyResponseChallenge()
|
||||
{
|
||||
ApplyResponseChallengeAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
protected override async Task ApplyResponseChallengeAsync()
|
||||
{
|
||||
if ((Response.StatusCode != 401) || (ChallengeContext == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await Options.Notifications.ApplyChallenge(new AuthenticationChallengeNotification<OAuthBearerAuthenticationOptions>(Context, Options));
|
||||
}
|
||||
|
||||
protected override void ApplyResponseGrant()
|
||||
{
|
||||
// N/A
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Security.DataProtection;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
/// <summary>
|
||||
/// Bearer authentication middleware component which is added to an HTTP pipeline. This class is not
|
||||
/// created by application code directly, instead it is added by calling the the IAppBuilder UseOAuthBearerAuthentication
|
||||
/// extension method.
|
||||
/// </summary>
|
||||
public class OAuthBearerAuthenticationMiddleware : AuthenticationMiddleware<OAuthBearerAuthenticationOptions>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Bearer authentication component which is added to an HTTP pipeline. This constructor is not
|
||||
/// called by application code directly, instead it is added by calling the the IAppBuilder UseOAuthBearerAuthentication
|
||||
/// extension method.
|
||||
/// </summary>
|
||||
public OAuthBearerAuthenticationMiddleware(
|
||||
RequestDelegate next,
|
||||
IServiceProvider services,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<OAuthBearerAuthenticationOptions> options,
|
||||
ConfigureOptions<OAuthBearerAuthenticationOptions> configureOptions)
|
||||
: base(next, services, options, configureOptions)
|
||||
{
|
||||
_logger = loggerFactory.Create<OAuthBearerAuthenticationMiddleware>();
|
||||
if (Options.Notifications == null)
|
||||
{
|
||||
Options.Notifications = new OAuthBearerAuthenticationNotifications();
|
||||
}
|
||||
|
||||
if (Options.SecurityTokenValidators == null)
|
||||
{
|
||||
Options.SecurityTokenValidators = new List<ISecurityTokenValidator> { new JwtSecurityTokenHandler() };
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Options.TokenValidationParameters.ValidAudience) && !string.IsNullOrWhiteSpace(Options.Audience))
|
||||
{
|
||||
Options.TokenValidationParameters.ValidAudience = Options.Audience;
|
||||
}
|
||||
|
||||
if (Options.ConfigurationManager == null)
|
||||
{
|
||||
if (Options.Configuration != null)
|
||||
{
|
||||
Options.ConfigurationManager = new StaticConfigurationManager<OpenIdConnectConfiguration>(Options.Configuration);
|
||||
}
|
||||
else if (!(string.IsNullOrWhiteSpace(Options.MetadataAddress) && string.IsNullOrWhiteSpace(Options.Authority)))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Options.MetadataAddress) && !string.IsNullOrWhiteSpace(Options.Authority))
|
||||
{
|
||||
Options.MetadataAddress = Options.Authority;
|
||||
if (!Options.MetadataAddress.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
Options.MetadataAddress += "/";
|
||||
}
|
||||
|
||||
Options.MetadataAddress += ".well-known/openid-configuration";
|
||||
}
|
||||
|
||||
HttpClient httpClient = new HttpClient(ResolveHttpMessageHandler(Options));
|
||||
httpClient.Timeout = Options.BackchannelTimeout;
|
||||
httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB
|
||||
|
||||
Options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(Options.MetadataAddress, httpClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the AuthenticationMiddleware base class to create a per-request handler.
|
||||
/// </summary>
|
||||
/// <returns>A new instance of the request handler</returns>
|
||||
protected override AuthenticationHandler<OAuthBearerAuthenticationOptions> CreateHandler()
|
||||
{
|
||||
return new OAuthBearerAuthenticationHandler(_logger);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")]
|
||||
private static HttpMessageHandler ResolveHttpMessageHandler(OAuthBearerAuthenticationOptions options)
|
||||
{
|
||||
HttpMessageHandler handler = options.BackchannelHttpHandler ??
|
||||
#if ASPNET50
|
||||
new WebRequestHandler();
|
||||
// If they provided a validator, apply it or fail.
|
||||
if (options.BackchannelCertificateValidator != null)
|
||||
{
|
||||
// Set the cert validate callback
|
||||
var webRequestHandler = handler as WebRequestHandler;
|
||||
if (webRequestHandler == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
|
||||
}
|
||||
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
|
||||
}
|
||||
#else
|
||||
new WinHttpHandler();
|
||||
#endif
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
/// <summary>
|
||||
/// Options class provides information needed to control Bearer Authentication middleware behavior
|
||||
/// </summary>
|
||||
public class OAuthBearerAuthenticationOptions : AuthenticationOptions
|
||||
{
|
||||
private ICollection<ISecurityTokenValidator> _securityTokenValidators;
|
||||
private TokenValidationParameters _tokenValidationParameters;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of bearer authentication options with default values.
|
||||
/// </summary>
|
||||
public OAuthBearerAuthenticationOptions() : base()
|
||||
{
|
||||
AuthenticationType = OAuthBearerAuthenticationDefaults.AuthenticationType;
|
||||
BackchannelTimeout = TimeSpan.FromMinutes(1);
|
||||
Challenge = OAuthBearerAuthenticationDefaults.AuthenticationType;
|
||||
Notifications = new OAuthBearerAuthenticationNotifications();
|
||||
RefreshOnIssuerKeyNotFound = true;
|
||||
SystemClock = new SystemClock();
|
||||
TokenValidationParameters = new TokenValidationParameters();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the discovery endpoint for obtaining metadata
|
||||
/// </summary>
|
||||
public string MetadataAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Authority to use when making OpenIdConnect calls.
|
||||
/// </summary>
|
||||
public string Authority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audience for any received JWT token.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The expected audience for any received JWT token.
|
||||
/// </value>
|
||||
public string Audience { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the challenge to put in the "WWW-Authenticate" header.
|
||||
/// </summary>
|
||||
/// TODO - brentschmaltz, should not be null.
|
||||
public string Challenge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The object provided by the application to process events raised by the bearer authentication middleware.
|
||||
/// The application may implement the interface fully, or it may create an instance of OAuthBearerAuthenticationProvider
|
||||
/// and assign delegates only to the events it wants to process.
|
||||
/// </summary>
|
||||
public OAuthBearerAuthenticationNotifications Notifications { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The HttpMessageHandler used to retrieve metadata.
|
||||
/// This cannot be set at the same time as BackchannelCertificateValidator unless the value
|
||||
/// is a WebRequestHandler.
|
||||
/// </summary>
|
||||
public HttpMessageHandler BackchannelHttpHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout when using the backchannel to make an http call.
|
||||
/// </summary>
|
||||
public TimeSpan BackchannelTimeout { get; set; }
|
||||
|
||||
#if ASPNET50
|
||||
/// <summary>
|
||||
/// Gets or sets the a pinned certificate validator to use to validate the endpoints used
|
||||
/// when retrieving metadata.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The pinned certificate validator.
|
||||
/// </value>
|
||||
/// <remarks>If this property is null then the default certificate checks are performed,
|
||||
/// validating the subject name and if the signing chain is a trusted party.</remarks>
|
||||
public ICertificateValidator BackchannelCertificateValidator { get; set; }
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Configuration provided directly by the developer. If provided, then MetadataAddress and the Backchannel properties
|
||||
/// will not be used. This information should not be updated during request processing.
|
||||
/// </summary>
|
||||
public OpenIdConnectConfiguration Configuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for retrieving, caching, and refreshing the configuration from metadata.
|
||||
/// If not provided, then one will be created using the MetadataAddress and Backchannel properties.
|
||||
/// </summary>
|
||||
public IConfigurationManager<OpenIdConnectConfiguration> ConfigurationManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if a metadata refresh should be attempted after a SecurityTokenSignatureKeyNotFoundException. This allows for automatic
|
||||
/// recovery in the event of a signature key rollover. This is enabled by default.
|
||||
/// </summary>
|
||||
public bool RefreshOnIssuerKeyNotFound { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to know what the current clock time is when calculating or validating token expiration. When not assigned default is based on
|
||||
/// DateTimeOffset.UtcNow. This is typically needed only for unit testing.
|
||||
/// </summary>
|
||||
public ISystemClock SystemClock { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="SecurityTokenValidators"/> for validating tokens.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">if 'value' is null.</exception>
|
||||
public ICollection<ISecurityTokenValidator> SecurityTokenValidators
|
||||
{
|
||||
get
|
||||
{
|
||||
return _securityTokenValidators;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("SecurityTokenValidators");
|
||||
}
|
||||
|
||||
_securityTokenValidators = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the TokenValidationParameters
|
||||
/// </summary>
|
||||
/// <remarks>Contains the types and definitions required for validating a token.</remarks>
|
||||
/// <exception cref="ArgumentNullException">if 'value' is null.</exception>
|
||||
public TokenValidationParameters TokenValidationParameters
|
||||
{
|
||||
get
|
||||
{
|
||||
return _tokenValidationParameters;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("TokenValidationParameters");
|
||||
}
|
||||
|
||||
_tokenValidationParameters = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.33440
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.Security.OAuth.Resources", System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(Resources)).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The '{0}' option must be provided..
|
||||
/// </summary>
|
||||
internal static string Exception_OptionMustBeProvided
|
||||
{
|
||||
get
|
||||
{
|
||||
return ResourceManager.GetString("Exception_OptionMustBeProvided", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler..
|
||||
/// </summary>
|
||||
internal static string Exception_ValidatorHandlerMismatch {
|
||||
get {
|
||||
return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Exception_OptionMustBeProvided" xml:space="preserve">
|
||||
<value>The '{0}' option must be provided.</value>
|
||||
</data>
|
||||
<data name="Exception_ValidatorHandlerMismatch" xml:space="preserve">
|
||||
<value>An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"description": "ASP.NET 5 middleware that enables an application to receive a OAuth bearer token.",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Security": "1.0.0-*",
|
||||
"Microsoft.IdentityModel.Protocol.Extensions": "2.0.0-beta1-*",
|
||||
"System.IdentityModel.Tokens": "5.0.0-beta1-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": {
|
||||
"frameworkAssemblies": {
|
||||
"System.Net.Http.WebRequest": "",
|
||||
"System.Net.Http": ""
|
||||
}
|
||||
},
|
||||
"aspnetcore50": {
|
||||
"dependencies": {
|
||||
"System.Net.Http.WinHttpHandler": "4.0.0-beta-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Security.OpenIdConnect
|
||||
{
|
||||
public interface INonceCache
|
||||
{
|
||||
string AddNonce(string nonce);
|
||||
bool TryRemoveNonce(string nonce);
|
||||
bool HasNonce(string nonce);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>674d128e-83bb-481a-a9d9-6d47872e1fc8</ProjectGuid>
|
||||
<RootNamespace>Microsoft.AspNet.Security.OpenIdConnect</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<UserProperties Project_1json__JSONSchema="http://www.asp.net/media/4878834/project.json" />
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Security.OpenIdConnect;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IdentityModel.Tokens;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// This Notification can be used to be informed when an 'AuthorizationCode' is received over the OpenIdConnect protocol.
|
||||
/// </summary>
|
||||
public class AuthorizationCodeReceivedNotification : BaseNotification<OpenIdConnectAuthenticationOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="AuthorizationCodeReceivedNotification"/>
|
||||
/// </summary>
|
||||
public AuthorizationCodeReceivedNotification(HttpContext context, OpenIdConnectAuthenticationOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'code'.
|
||||
/// </summary>
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="JwtSecurityToken"/> that was received in the id_token + code OpenIdConnectRequest.
|
||||
/// </summary>
|
||||
public JwtSecurityToken JwtSecurityToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="OpenIdConnectMessage"/>.
|
||||
/// </summary>
|
||||
public OpenIdConnectMessage ProtocolMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'redirect_uri'.
|
||||
/// </summary>
|
||||
/// <remarks>This is the redirect_uri that was sent in the id_token + code OpenIdConnectRequest.</remarks>
|
||||
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "user controlled, not necessarily a URI")]
|
||||
public string RedirectUri { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Security.OpenIdConnect
|
||||
{
|
||||
/// <summary>
|
||||
/// Default values related to OpenIdConnect authentication middleware
|
||||
/// </summary>
|
||||
public static class OpenIdConnectAuthenticationDefaults
|
||||
{
|
||||
/// <summary>
|
||||
/// The default value used for OpenIdConnectAuthenticationOptions.AuthenticationType
|
||||
/// </summary>
|
||||
public const string AuthenticationType = "OpenIdConnect";
|
||||
|
||||
/// <summary>
|
||||
/// The prefix used to provide a default OpenIdConnectAuthenticationOptions.CookieName
|
||||
/// </summary>
|
||||
public const string CookiePrefix = ".AspNet.OpenIdConnect.";
|
||||
|
||||
/// <summary>
|
||||
/// The default value for OpenIdConnectAuthenticationOptions.Caption.
|
||||
/// </summary>
|
||||
public const string Caption = "OpenIdConnect";
|
||||
|
||||
/// <summary>
|
||||
/// The prefix used to for the a nonce in the cookie
|
||||
/// </summary>
|
||||
internal const string CookieNoncePrefix = ".AspNet.OpenIdConnect.Nonce.";
|
||||
|
||||
/// <summary>
|
||||
/// The property for the RedirectUri that was used when asking for a 'authorizationCode'
|
||||
/// </summary>
|
||||
public const string RedirectUriUsedForCodeKey = "OpenIdConnect.Code.RedirectUri";
|
||||
|
||||
/// <summary>
|
||||
/// Constant used to identify state in openIdConnect protocal message
|
||||
/// </summary>
|
||||
internal const string AuthenticationPropertiesKey = "OpenIdConnect.AuthenticationProperties";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Security.OpenIdConnect;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
||||
namespace Microsoft.AspNet.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for using <see cref="OpenIdConnectAuthenticationMiddleware"/>
|
||||
/// </summary>
|
||||
public static class OpenIdConnectAuthenticationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the <see cref="OpenIdConnectAuthenticationMiddleware"/> into the ASP.NET runtime.
|
||||
/// </summary>
|
||||
/// <param name="app">The application builder</param>
|
||||
/// <param name="options">Options which control the processing of the OpenIdConnect protocol and token validation.</param>
|
||||
/// <returns>The application builder</returns>
|
||||
public static IApplicationBuilder UseOpenIdConnectAuthentication(this IApplicationBuilder app, Action<OpenIdConnectAuthenticationOptions> configureOptions = null, string optionsName = "")
|
||||
{
|
||||
return app.UseMiddleware<OpenIdConnectAuthenticationMiddleware>(
|
||||
new ConfigureOptions<OpenIdConnectAuthenticationOptions>(configureOptions ?? (o => { }))
|
||||
{
|
||||
Name = optionsName
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Security.DataHandler;
|
||||
using Microsoft.AspNet.Security.DataHandler.Encoder;
|
||||
using Microsoft.AspNet.Security.DataHandler.Serializer;
|
||||
using Microsoft.AspNet.Security.DataProtection;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OpenIdConnect
|
||||
{
|
||||
/// <summary>
|
||||
/// ASP.NET middleware for obtaining identities using OpenIdConnect protocol.
|
||||
/// </summary>
|
||||
public class OpenIdConnectAuthenticationMiddleware : AuthenticationMiddleware<OpenIdConnectAuthenticationOptions>
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="OpenIdConnectAuthenticationMiddleware"/>
|
||||
/// </summary>
|
||||
/// <param name="next">The next middleware in the ASP.NET pipeline to invoke</param>
|
||||
/// <param name="app">The ASP.NET application</param>
|
||||
/// <param name="options">Configuration options for the middleware</param>
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")]
|
||||
public OpenIdConnectAuthenticationMiddleware(
|
||||
RequestDelegate next,
|
||||
IServiceProvider services,
|
||||
IDataProtectionProvider dataProtectionProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<ExternalAuthenticationOptions> externalOptions,
|
||||
IOptions<OpenIdConnectAuthenticationOptions> options,
|
||||
ConfigureOptions<OpenIdConnectAuthenticationOptions> configureOptions)
|
||||
: base(next, services, options, configureOptions)
|
||||
{
|
||||
_logger = loggerFactory.Create<OpenIdConnectAuthenticationMiddleware>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Options.TokenValidationParameters.AuthenticationType))
|
||||
{
|
||||
Options.TokenValidationParameters.AuthenticationType = externalOptions.Options.SignInAsAuthenticationType;
|
||||
}
|
||||
|
||||
if (Options.StateDataFormat == null)
|
||||
{
|
||||
var dataProtector = dataProtectionProvider.CreateDataProtector(
|
||||
typeof(OpenIdConnectAuthenticationMiddleware).FullName,
|
||||
typeof(string).FullName,
|
||||
Options.AuthenticationType,
|
||||
"v1");
|
||||
|
||||
Options.StateDataFormat = new PropertiesDataFormat(dataProtector);
|
||||
}
|
||||
|
||||
if (Options.StringDataFormat == null)
|
||||
{
|
||||
var dataProtector = dataProtectionProvider.CreateDataProtector(
|
||||
typeof(OpenIdConnectAuthenticationMiddleware).FullName,
|
||||
typeof(string).FullName,
|
||||
Options.AuthenticationType,
|
||||
"v1");
|
||||
|
||||
Options.StringDataFormat = new SecureDataFormat<string>(new StringSerializer(), dataProtector, TextEncodings.Base64Url);
|
||||
}
|
||||
|
||||
if (Options.SecurityTokenValidators == null)
|
||||
{
|
||||
Options.SecurityTokenValidators = new Collection<ISecurityTokenValidator> { new JwtSecurityTokenHandler() };
|
||||
}
|
||||
|
||||
// if the user has not set the AuthorizeCallback, set it from the redirect_uri
|
||||
if (!Options.CallbackPath.HasValue)
|
||||
{
|
||||
Uri redirectUri;
|
||||
if (!string.IsNullOrEmpty(Options.RedirectUri) && Uri.TryCreate(Options.RedirectUri, UriKind.Absolute, out redirectUri))
|
||||
{
|
||||
// Redirect_Uri must be a very specific, case sensitive value, so we can't generate it. Instead we generate AuthorizeCallback from it.
|
||||
Options.CallbackPath = PathString.FromUriComponent(redirectUri);
|
||||
}
|
||||
}
|
||||
|
||||
if (Options.Notifications == null)
|
||||
{
|
||||
Options.Notifications = new OpenIdConnectAuthenticationNotifications();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Options.TokenValidationParameters.ValidAudience) && !string.IsNullOrWhiteSpace(Options.ClientId))
|
||||
{
|
||||
Options.TokenValidationParameters.ValidAudience = Options.ClientId;
|
||||
}
|
||||
|
||||
if (Options.ConfigurationManager == null)
|
||||
{
|
||||
if (Options.Configuration != null)
|
||||
{
|
||||
Options.ConfigurationManager = new StaticConfigurationManager<OpenIdConnectConfiguration>(Options.Configuration);
|
||||
}
|
||||
else if (!(string.IsNullOrWhiteSpace(Options.MetadataAddress) && string.IsNullOrWhiteSpace(Options.Authority)))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Options.MetadataAddress) && !string.IsNullOrWhiteSpace(Options.Authority))
|
||||
{
|
||||
Options.MetadataAddress = Options.Authority;
|
||||
if (!Options.MetadataAddress.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
Options.MetadataAddress += "/";
|
||||
}
|
||||
|
||||
Options.MetadataAddress += ".well-known/openid-configuration";
|
||||
}
|
||||
|
||||
HttpClient httpClient = new HttpClient(ResolveHttpMessageHandler(Options));
|
||||
httpClient.Timeout = Options.BackchannelTimeout;
|
||||
httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB
|
||||
Options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(Options.MetadataAddress, httpClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the <see cref="AuthenticationHandler"/> object for processing authentication-related requests.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="AuthenticationHandler"/> configured with the <see cref="OpenIdConnectAuthenticationOptions"/> supplied to the constructor.</returns>
|
||||
protected override AuthenticationHandler<OpenIdConnectAuthenticationOptions> CreateHandler()
|
||||
{
|
||||
return new OpenIdConnectAuthenticationHandler(_logger);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")]
|
||||
private static HttpMessageHandler ResolveHttpMessageHandler(OpenIdConnectAuthenticationOptions options)
|
||||
{
|
||||
HttpMessageHandler handler = options.BackchannelHttpHandler ??
|
||||
#if ASPNET50
|
||||
new WebRequestHandler();
|
||||
// If they provided a validator, apply it or fail.
|
||||
if (options.BackchannelCertificateValidator != null)
|
||||
{
|
||||
// Set the cert validate callback
|
||||
var webRequestHandler = handler as WebRequestHandler;
|
||||
if (webRequestHandler == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.Exception_ValidatorHandlerMismatch);
|
||||
}
|
||||
webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
|
||||
}
|
||||
#else
|
||||
new WinHttpHandler();
|
||||
#endif
|
||||
return handler;
|
||||
}
|
||||
|
||||
private class StringSerializer : IDataSerializer<string>
|
||||
{
|
||||
public string Deserialize(byte[] data)
|
||||
{
|
||||
return Encoding.UTF8.GetString(data);
|
||||
}
|
||||
|
||||
public byte[] Serialize(string model)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Security.Notifications;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OpenIdConnect
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies events which the <see cref="OpenIdConnectAuthenticationMiddleware" />invokes to enable developer control over the authentication process.
|
||||
/// </summary>
|
||||
public class OpenIdConnectAuthenticationNotifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new set of notifications. Each notification has a default no-op behavior unless otherwise documented.
|
||||
/// </summary>
|
||||
public OpenIdConnectAuthenticationNotifications()
|
||||
{
|
||||
AuthenticationFailed = notification => Task.FromResult(0);
|
||||
AuthorizationCodeReceived = notification => Task.FromResult(0);
|
||||
MessageReceived = notification => Task.FromResult(0);
|
||||
SecurityTokenReceived = notification => Task.FromResult(0);
|
||||
SecurityTokenValidated = notification => Task.FromResult(0);
|
||||
RedirectToIdentityProvider = notification => Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed.
|
||||
/// </summary>
|
||||
public Func<AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> AuthenticationFailed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked after security token validation if an authorization code is present in the protocol message.
|
||||
/// </summary>
|
||||
public Func<AuthorizationCodeReceivedNotification, Task> AuthorizationCodeReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a protocol message is first received.
|
||||
/// </summary>
|
||||
public Func<MessageReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> MessageReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked to manipulate redirects to the identity provider for SignIn, SignOut, or Challenge.
|
||||
/// </summary>
|
||||
public Func<RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> RedirectToIdentityProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked with the security token that has been extracted from the protocol message.
|
||||
/// </summary>
|
||||
public Func<SecurityTokenReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> SecurityTokenReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked after the security token has passed validation and a ClaimsIdentity has been generated.
|
||||
/// </summary>
|
||||
public Func<SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> SecurityTokenValidated { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,338 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OpenIdConnect
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration options for <see cref="OpenIdConnectAuthenticationOptions"/>
|
||||
/// </summary>
|
||||
public class OpenIdConnectAuthenticationOptions : AuthenticationOptions
|
||||
{
|
||||
private TimeSpan _backchannelTimeout;
|
||||
private OpenIdConnectProtocolValidator _protocolValidator;
|
||||
private ICollection<ISecurityTokenValidator> _securityTokenValidators;
|
||||
private ISecureDataFormat<AuthenticationProperties> _stateDataFormat;
|
||||
private ISecureDataFormat<string> _stringDataFormat;
|
||||
private TokenValidationParameters _tokenValidationParameters;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="OpenIdConnectAuthenticationOptions"/>
|
||||
/// </summary>
|
||||
public OpenIdConnectAuthenticationOptions()
|
||||
: this(OpenIdConnectAuthenticationDefaults.AuthenticationType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="OpenIdConnectAuthenticationOptions"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Defaults:
|
||||
/// <para>AddNonceToRequest: true.</para>
|
||||
/// <para>AuthenticationMode: <see cref="AuthenticationMode.Active"/>.</para>
|
||||
/// <para>BackchannelTimeout: 1 minute.</para>
|
||||
/// <para>Caption: <see cref="OpenIdConnectAuthenticationDefaults.Caption"/>.</para>
|
||||
/// <para>ProtocolValidator: new <see cref="OpenIdConnectProtocolValidator"/>.</para>
|
||||
/// <para>RefreshOnIssuerKeyNotFound: true</para>
|
||||
/// <para>ResponseType: <see cref="OpenIdConnectResponseTypes.CodeIdToken"/></para>
|
||||
/// <para>Scope: <see cref="OpenIdConnectScopes.OpenIdProfile"/>.</para>
|
||||
/// <para>TokenValidationParameters: new <see cref="TokenValidationParameters"/> with AuthenticationType = authenticationType.</para>
|
||||
/// <para>UseTokenLifetime: true.</para>
|
||||
/// </remarks>
|
||||
/// <param name="authenticationType"> will be used to when creating the <see cref="System.Security.Claims.ClaimsIdentity"/> for the AuthenticationType property.</param>
|
||||
[SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationOptions.set_Caption(System.String)", Justification = "Not a LOC field")]
|
||||
public OpenIdConnectAuthenticationOptions(string authenticationType)
|
||||
{
|
||||
AuthenticationMode = AuthenticationMode.Active;
|
||||
AuthenticationType = authenticationType;
|
||||
BackchannelTimeout = TimeSpan.FromMinutes(1);
|
||||
Caption = OpenIdConnectAuthenticationDefaults.Caption;
|
||||
ProtocolValidator = new OpenIdConnectProtocolValidator();
|
||||
RefreshOnIssuerKeyNotFound = true;
|
||||
ResponseType = OpenIdConnectResponseTypes.CodeIdToken;
|
||||
Scope = OpenIdConnectScopes.OpenIdProfile;
|
||||
TokenValidationParameters = new TokenValidationParameters();
|
||||
UseTokenLifetime = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Authority to use when making OpenIdConnect calls.
|
||||
/// </summary>
|
||||
public string Authority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional constrained path on which to process the authentication callback.
|
||||
/// If not provided and RedirectUri is available, this value will be generated from RedirectUri.
|
||||
/// </summary>
|
||||
/// <remarks>If you set this value, then the <see cref="OpenIdConnectAuthenticationHandler"/> will only listen for posts at this address.
|
||||
/// If the IdentityProvider does not post to this address, you may end up in a 401 -> IdentityProvider -> Client -> 401 -> ...</remarks>
|
||||
public PathString CallbackPath { get; set; }
|
||||
|
||||
#if ASPNET50
|
||||
/// <summary>
|
||||
/// Gets or sets the a pinned certificate validator to use to validate the endpoints used
|
||||
/// when retrieving metadata.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The pinned certificate validator.
|
||||
/// </value>
|
||||
/// <remarks>If this property is null then the default certificate checks are performed,
|
||||
/// validating the subject name and if the signing chain is a trusted party.</remarks>
|
||||
public ICertificateValidator BackchannelCertificateValidator { get; set; }
|
||||
#endif
|
||||
/// <summary>
|
||||
/// The HttpMessageHandler used to retrieve metadata.
|
||||
/// This cannot be set at the same time as BackchannelCertificateValidator unless the value
|
||||
/// is a WebRequestHandler.
|
||||
/// </summary>
|
||||
public HttpMessageHandler BackchannelHttpHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout when using the backchannel to make an http call.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "By design we use the property name in the exception")]
|
||||
public TimeSpan BackchannelTimeout
|
||||
{
|
||||
get
|
||||
{
|
||||
return _backchannelTimeout;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value <= TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("BackchannelTimeout", value, Resources.ArgsException_BackchallelLessThanZero);
|
||||
}
|
||||
|
||||
_backchannelTimeout = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or sets the text that the user can display on a sign in user interface.
|
||||
/// </summary>
|
||||
public string Caption
|
||||
{
|
||||
get { return Description.Caption; }
|
||||
set { Description.Caption = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'client_id'.
|
||||
/// </summary>
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'client_secret'.
|
||||
/// </summary>
|
||||
public string ClientSecret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Configuration provided directly by the developer. If provided, then MetadataAddress and the Backchannel properties
|
||||
/// will not be used. This information should not be updated during request processing.
|
||||
/// </summary>
|
||||
public OpenIdConnectConfiguration Configuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The OpenIdConnect protocol http://openid.net/specs/openid-connect-core-1_0.html
|
||||
/// recommends adding a nonce to a request as a mitigation against replay attacks when requesting id_tokens.
|
||||
/// By default the runtime uses cookies with unique names generated from a hash of the nonce.
|
||||
/// </summary>
|
||||
public INonceCache NonceCache { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the discovery endpoint for obtaining metadata
|
||||
/// </summary>
|
||||
public string MetadataAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the expected audience for any received JWT token.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The expected audience for any received JWT token.
|
||||
/// </value>
|
||||
public string Audience { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for retrieving, caching, and refreshing the configuration from metadata.
|
||||
/// If not provided, then one will be created using the MetadataAddress and Backchannel properties.
|
||||
/// </summary>
|
||||
public IConfigurationManager<OpenIdConnectConfiguration> ConfigurationManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if a metadata refresh should be attempted after a SecurityTokenSignatureKeyNotFoundException. This allows for automatic
|
||||
/// recovery in the event of a signature key rollover. This is enabled by default.
|
||||
/// </summary>
|
||||
public bool RefreshOnIssuerKeyNotFound { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="OpenIdConnectAuthenticationNotifications"/> to notify when processing OpenIdConnect messages.
|
||||
/// </summary>
|
||||
public OpenIdConnectAuthenticationNotifications Notifications { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="OpenIdConnectProtocolValidator"/> that is used to ensure that the 'id_token' received
|
||||
/// is valid per: http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">if 'value' is null.</exception>
|
||||
public OpenIdConnectProtocolValidator ProtocolValidator
|
||||
{
|
||||
get
|
||||
{
|
||||
return _protocolValidator;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("value");
|
||||
}
|
||||
|
||||
_protocolValidator = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'post_logout_redirect_uri'
|
||||
/// </summary>
|
||||
/// <remarks>This is sent to the OP as the redirect for the user-agent.</remarks>
|
||||
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "By design")]
|
||||
[SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Logout", Justification = "This is the term used in the spec.")]
|
||||
public string PostLogoutRedirectUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'redirect_uri'.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "By Design")]
|
||||
public string RedirectUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'resource'.
|
||||
/// </summary>
|
||||
public string Resource { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'response_type'.
|
||||
/// </summary>
|
||||
public string ResponseType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 'scope'.
|
||||
/// </summary>
|
||||
public string Scope { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AuthenticationType used when creating the <see cref="System.Security.Claims.ClaimsIdentity"/>.
|
||||
/// </summary>
|
||||
public string SignInAsAuthenticationType
|
||||
{
|
||||
get { return TokenValidationParameters.AuthenticationType; }
|
||||
set { TokenValidationParameters.AuthenticationType = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type used to secure data handled by the middleware.
|
||||
/// </summary>
|
||||
public ISecureDataFormat<AuthenticationProperties> StateDataFormat
|
||||
{
|
||||
get
|
||||
{
|
||||
return _stateDataFormat;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("value");
|
||||
}
|
||||
|
||||
_stateDataFormat = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type used to secure strings used by the middleware.
|
||||
/// </summary>
|
||||
public ISecureDataFormat<string> StringDataFormat
|
||||
{
|
||||
get
|
||||
{
|
||||
return _stringDataFormat;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("value");
|
||||
}
|
||||
|
||||
_stringDataFormat = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="SecurityTokenValidators"/> for validating tokens.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">if 'value' is null.</exception>
|
||||
public ICollection<ISecurityTokenValidator> SecurityTokenValidators
|
||||
{
|
||||
get
|
||||
{
|
||||
return _securityTokenValidators;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("SecurityTokenValidators");
|
||||
}
|
||||
|
||||
_securityTokenValidators = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the TokenValidationParameters
|
||||
/// </summary>
|
||||
/// <remarks>Contains the types and definitions required for validating a token.</remarks>
|
||||
public TokenValidationParameters TokenValidationParameters
|
||||
{
|
||||
get
|
||||
{
|
||||
return _tokenValidationParameters;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("value");
|
||||
}
|
||||
|
||||
_tokenValidationParameters = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the authentication session lifetime (e.g. cookies) should match that of the authentication token.
|
||||
/// If the token does not provide lifetime information then normal session lifetimes will be used.
|
||||
/// This is enabled by default.
|
||||
/// </summary>
|
||||
public bool UseTokenLifetime
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,567 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.AspNet.Security.Notifications;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OpenIdConnect
|
||||
{
|
||||
/// <summary>
|
||||
/// A per-request authentication handler for the OpenIdConnectAuthenticationMiddleware.
|
||||
/// </summary>
|
||||
public class OpenIdConnectAuthenticationHandler : AuthenticationHandler<OpenIdConnectAuthenticationOptions>
|
||||
{
|
||||
private const string NonceProperty = "N";
|
||||
private const string UriSchemeDelimiter = "://";
|
||||
private readonly ILogger _logger;
|
||||
private OpenIdConnectConfiguration _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new OpenIdConnectAuthenticationHandler
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
public OpenIdConnectAuthenticationHandler(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private string CurrentUri
|
||||
{
|
||||
get
|
||||
{
|
||||
return Request.Scheme +
|
||||
UriSchemeDelimiter +
|
||||
Request.Host +
|
||||
Request.PathBase +
|
||||
Request.Path +
|
||||
Request.QueryString;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyResponseGrant()
|
||||
{
|
||||
ApplyResponseGrantAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles Signout
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override async Task ApplyResponseGrantAsync()
|
||||
{
|
||||
var signout = SignOutContext;
|
||||
if (signout != null)
|
||||
{
|
||||
if (_configuration == null && Options.ConfigurationManager != null)
|
||||
{
|
||||
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
|
||||
}
|
||||
|
||||
OpenIdConnectMessage openIdConnectMessage = new OpenIdConnectMessage()
|
||||
{
|
||||
IssuerAddress = _configuration == null ? string.Empty : (_configuration.EndSessionEndpoint ?? string.Empty),
|
||||
RequestType = OpenIdConnectRequestType.LogoutRequest,
|
||||
};
|
||||
|
||||
// Set End_Session_Endpoint in order:
|
||||
// 1. properties.Redirect
|
||||
// 2. Options.Wreply
|
||||
AuthenticationProperties properties = new AuthenticationProperties();
|
||||
if (properties != null && !string.IsNullOrEmpty(properties.RedirectUri))
|
||||
{
|
||||
openIdConnectMessage.PostLogoutRedirectUri = properties.RedirectUri;
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(Options.PostLogoutRedirectUri))
|
||||
{
|
||||
openIdConnectMessage.PostLogoutRedirectUri = Options.PostLogoutRedirectUri;
|
||||
}
|
||||
|
||||
var notification = new RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = openIdConnectMessage
|
||||
};
|
||||
await Options.Notifications.RedirectToIdentityProvider(notification);
|
||||
|
||||
if (!notification.HandledResponse)
|
||||
{
|
||||
string redirectUri = notification.ProtocolMessage.CreateLogoutRequestUrl();
|
||||
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
|
||||
{
|
||||
_logger.WriteWarning("The logout redirect URI is malformed: " + redirectUri);
|
||||
}
|
||||
Response.Redirect(redirectUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyResponseChallenge()
|
||||
{
|
||||
ApplyResponseChallengeAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Responds to a 401 Challenge. Sends an OpenIdConnect message to the 'identity authority' to obtain an identity.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override async Task ApplyResponseChallengeAsync()
|
||||
{
|
||||
if ((Response.StatusCode != 401) || (ChallengeContext == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// order for redirect_uri
|
||||
// 1. challenge.Properties.RedirectUri
|
||||
// 2. CurrentUri
|
||||
AuthenticationProperties properties = new AuthenticationProperties(ChallengeContext.Properties);
|
||||
if (string.IsNullOrEmpty(properties.RedirectUri))
|
||||
{
|
||||
properties.RedirectUri = CurrentUri;
|
||||
}
|
||||
|
||||
// this value will be passed to the AuthorizationCodeReceivedNotification
|
||||
if (!string.IsNullOrWhiteSpace(Options.RedirectUri))
|
||||
{
|
||||
properties.Dictionary.Add(OpenIdConnectAuthenticationDefaults.RedirectUriUsedForCodeKey, Options.RedirectUri);
|
||||
}
|
||||
|
||||
if (_configuration == null && Options.ConfigurationManager != null)
|
||||
{
|
||||
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
|
||||
}
|
||||
|
||||
OpenIdConnectMessage openIdConnectMessage = new OpenIdConnectMessage
|
||||
{
|
||||
ClientId = Options.ClientId,
|
||||
IssuerAddress = _configuration == null ? string.Empty : (_configuration.AuthorizationEndpoint ?? string.Empty),
|
||||
RedirectUri = Options.RedirectUri,
|
||||
RequestType = OpenIdConnectRequestType.AuthenticationRequest,
|
||||
Resource = Options.Resource,
|
||||
ResponseMode = OpenIdConnectResponseModes.FormPost,
|
||||
ResponseType = Options.ResponseType,
|
||||
Scope = Options.Scope,
|
||||
State = OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey + "=" + Uri.EscapeDataString(Options.StateDataFormat.Protect(properties))
|
||||
};
|
||||
|
||||
// TODO - brentschmaltz, if INonceCache is set should we even consider if ProtocolValidator is set?
|
||||
if (Options.ProtocolValidator.RequireNonce)
|
||||
{
|
||||
openIdConnectMessage.Nonce = Options.ProtocolValidator.GenerateNonce();
|
||||
if (Options.NonceCache != null)
|
||||
{
|
||||
Options.NonceCache.AddNonce(openIdConnectMessage.Nonce);
|
||||
}
|
||||
else
|
||||
{
|
||||
RememberNonce(openIdConnectMessage.Nonce);
|
||||
}
|
||||
}
|
||||
|
||||
var notification = new RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = openIdConnectMessage
|
||||
};
|
||||
|
||||
await Options.Notifications.RedirectToIdentityProvider(notification);
|
||||
if (!notification.HandledResponse)
|
||||
{
|
||||
string redirectUri = notification.ProtocolMessage.CreateAuthenticationRequestUrl();
|
||||
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
|
||||
{
|
||||
_logger.WriteWarning("The authenticate redirect URI is malformed: " + redirectUri);
|
||||
}
|
||||
|
||||
Response.Redirect(redirectUri);
|
||||
}
|
||||
}
|
||||
|
||||
protected override AuthenticationTicket AuthenticateCore()
|
||||
{
|
||||
return AuthenticateCoreAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked to process incoming OpenIdConnect messages.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="AuthenticationTicket"/> if successful.</returns>
|
||||
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
|
||||
{
|
||||
// Allow login to be constrained to a specific path. Need to make this runtime configurable.
|
||||
if (Options.CallbackPath.HasValue && Options.CallbackPath != (Request.PathBase + Request.Path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
OpenIdConnectMessage openIdConnectMessage = null;
|
||||
|
||||
// assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small.
|
||||
if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.IsNullOrWhiteSpace(Request.ContentType)
|
||||
// May have media/type; charset=utf-8, allow partial match.
|
||||
&& Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)
|
||||
&& Request.Body.CanRead)
|
||||
{
|
||||
IFormCollection form = await Request.ReadFormAsync();
|
||||
Request.Body.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
openIdConnectMessage = new OpenIdConnectMessage(form);
|
||||
}
|
||||
|
||||
if (openIdConnectMessage == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var messageReceivedNotification = new MessageReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = openIdConnectMessage
|
||||
};
|
||||
|
||||
await Options.Notifications.MessageReceived(messageReceivedNotification);
|
||||
if (messageReceivedNotification.HandledResponse)
|
||||
{
|
||||
return messageReceivedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (messageReceivedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// runtime always adds state, if we don't find it OR we failed to 'unprotect' it this is not a message we
|
||||
// should process.
|
||||
AuthenticationProperties properties = GetPropertiesFromState(openIdConnectMessage.State);
|
||||
if (properties == null)
|
||||
{
|
||||
_logger.WriteWarning("The state field is missing or invalid.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// devs will need to hook AuthenticationFailedNotification to avoid having 'raw' runtime errors displayed to users.
|
||||
if (!string.IsNullOrWhiteSpace(openIdConnectMessage.Error))
|
||||
{
|
||||
throw new OpenIdConnectProtocolException(
|
||||
string.Format(CultureInfo.InvariantCulture,
|
||||
openIdConnectMessage.Error,
|
||||
Resources.Exception_OpenIdConnectMessageError, openIdConnectMessage.ErrorDescription ?? string.Empty, openIdConnectMessage.ErrorUri ?? string.Empty));
|
||||
}
|
||||
|
||||
// code is only accepted with id_token, in this version, hence check for code is inside this if
|
||||
// OpenIdConnect protocol allows a Code to be received without the id_token
|
||||
if (string.IsNullOrWhiteSpace(openIdConnectMessage.IdToken))
|
||||
{
|
||||
_logger.WriteWarning("The id_token is missing.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var securityTokenReceivedNotification = new SecurityTokenReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = openIdConnectMessage
|
||||
};
|
||||
|
||||
await Options.Notifications.SecurityTokenReceived(securityTokenReceivedNotification);
|
||||
if (securityTokenReceivedNotification.HandledResponse)
|
||||
{
|
||||
return securityTokenReceivedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (securityTokenReceivedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_configuration == null && Options.ConfigurationManager != null)
|
||||
{
|
||||
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
|
||||
}
|
||||
|
||||
// Copy and augment to avoid cross request race conditions for updated configurations.
|
||||
TokenValidationParameters validationParameters = Options.TokenValidationParameters.Clone();
|
||||
if (_configuration != null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer))
|
||||
{
|
||||
validationParameters.ValidIssuer = _configuration.Issuer;
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(_configuration.Issuer))
|
||||
{
|
||||
validationParameters.ValidIssuers = (validationParameters.ValidIssuers == null ? new[] { _configuration.Issuer } : validationParameters.ValidIssuers.Concat(new[] { _configuration.Issuer }));
|
||||
}
|
||||
|
||||
validationParameters.IssuerSigningKeys = (validationParameters.IssuerSigningKeys == null ? _configuration.SigningKeys : validationParameters.IssuerSigningKeys.Concat(_configuration.SigningKeys));
|
||||
}
|
||||
|
||||
AuthenticationTicket ticket;
|
||||
SecurityToken validatedToken = null;
|
||||
ClaimsPrincipal principal = null;
|
||||
JwtSecurityToken jwt = null;
|
||||
|
||||
foreach (var validator in Options.SecurityTokenValidators)
|
||||
{
|
||||
if (validator.CanReadToken(openIdConnectMessage.IdToken))
|
||||
{
|
||||
principal = validator.ValidateToken(openIdConnectMessage.IdToken, validationParameters, out validatedToken);
|
||||
jwt = validatedToken as JwtSecurityToken;
|
||||
if (jwt == null)
|
||||
{
|
||||
throw new InvalidOperationException("Validated Security Token must be a JwtSecurityToken was: " + (validatedToken == null ? "null" : validatedToken.GetType().ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (validatedToken == null)
|
||||
{
|
||||
throw new InvalidOperationException("No SecurityTokenValidator found for token: " + openIdConnectMessage.IdToken);
|
||||
}
|
||||
|
||||
ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationType);
|
||||
if (!string.IsNullOrWhiteSpace(openIdConnectMessage.SessionState))
|
||||
{
|
||||
ticket.Properties.Dictionary[OpenIdConnectSessionProperties.SessionState] = openIdConnectMessage.SessionState;
|
||||
}
|
||||
|
||||
if (_configuration != null && !string.IsNullOrWhiteSpace(_configuration.CheckSessionIframe))
|
||||
{
|
||||
ticket.Properties.Dictionary[OpenIdConnectSessionProperties.CheckSessionIFrame] = _configuration.CheckSessionIframe;
|
||||
}
|
||||
|
||||
if (Options.UseTokenLifetime)
|
||||
{
|
||||
// Override any session persistence to match the token lifetime.
|
||||
DateTime issued = validatedToken.ValidFrom;
|
||||
if (issued != DateTime.MinValue)
|
||||
{
|
||||
ticket.Properties.IssuedUtc = issued;
|
||||
}
|
||||
|
||||
DateTime expires = validatedToken.ValidTo;
|
||||
if (expires != DateTime.MinValue)
|
||||
{
|
||||
ticket.Properties.ExpiresUtc = expires;
|
||||
}
|
||||
|
||||
ticket.Properties.AllowRefresh = false;
|
||||
}
|
||||
|
||||
var securityTokenValidatedNotification = new SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
AuthenticationTicket = ticket,
|
||||
ProtocolMessage = openIdConnectMessage
|
||||
};
|
||||
|
||||
await Options.Notifications.SecurityTokenValidated(securityTokenValidatedNotification);
|
||||
if (securityTokenValidatedNotification.HandledResponse)
|
||||
{
|
||||
return securityTokenValidatedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (securityTokenValidatedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var protocolValidationContext = new OpenIdConnectProtocolValidationContext
|
||||
{
|
||||
AuthorizationCode = openIdConnectMessage.Code,
|
||||
Nonce = RetrieveNonce(jwt.Payload.Nonce),
|
||||
};
|
||||
|
||||
Options.ProtocolValidator.Validate(jwt, protocolValidationContext);
|
||||
if (openIdConnectMessage.Code != null)
|
||||
{
|
||||
var authorizationCodeReceivedNotification = new AuthorizationCodeReceivedNotification(Context, Options)
|
||||
{
|
||||
AuthenticationTicket = ticket,
|
||||
Code = openIdConnectMessage.Code,
|
||||
JwtSecurityToken = jwt,
|
||||
ProtocolMessage = openIdConnectMessage,
|
||||
RedirectUri = ticket.Properties.Dictionary.ContainsKey(OpenIdConnectAuthenticationDefaults.RedirectUriUsedForCodeKey) ?
|
||||
ticket.Properties.Dictionary[OpenIdConnectAuthenticationDefaults.RedirectUriUsedForCodeKey] : string.Empty,
|
||||
};
|
||||
|
||||
await Options.Notifications.AuthorizationCodeReceived(authorizationCodeReceivedNotification);
|
||||
if (authorizationCodeReceivedNotification.HandledResponse)
|
||||
{
|
||||
return authorizationCodeReceivedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (authorizationCodeReceivedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return ticket;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.WriteError("Exception occurred while processing message", exception);
|
||||
|
||||
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the notification.
|
||||
if (Options.RefreshOnIssuerKeyNotFound && exception.GetType().Equals(typeof(SecurityTokenSignatureKeyNotFoundException)))
|
||||
{
|
||||
Options.ConfigurationManager.RequestRefresh();
|
||||
}
|
||||
|
||||
var authenticationFailedNotification = new AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
|
||||
{
|
||||
ProtocolMessage = openIdConnectMessage,
|
||||
Exception = exception
|
||||
};
|
||||
|
||||
await Options.Notifications.AuthenticationFailed(authenticationFailedNotification);
|
||||
if (authenticationFailedNotification.HandledResponse)
|
||||
{
|
||||
return authenticationFailedNotification.AuthenticationTicket;
|
||||
}
|
||||
|
||||
if (authenticationFailedNotification.Skipped)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the nonce to <see cref="HttpResponse.Cookies"/>.
|
||||
/// </summary>
|
||||
/// <param name="nonce">the nonce to remember.</param>
|
||||
/// <remarks><see cref="HttpResponse.Cookies.Append"/>is called to add a cookie with the name: 'OpenIdConnectAuthenticationDefaults.Nonce + <see cref="OpenIdConnectAuthenticationOptions.StringDataFormat.Protect"/>(nonce)'.
|
||||
/// The value of the cookie is: "N".</remarks>
|
||||
private void RememberNonce(string nonce)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(nonce))
|
||||
{
|
||||
throw new ArgumentNullException("nonce");
|
||||
}
|
||||
|
||||
Response.Cookies.Append(
|
||||
OpenIdConnectAuthenticationDefaults.CookieNoncePrefix + Options.StringDataFormat.Protect(nonce),
|
||||
NonceProperty,
|
||||
new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = Request.IsSecure
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches <see cref="HttpRequest.Cookies"/> for a matching nonce.
|
||||
/// </summary>
|
||||
/// <param name="nonceExpectedValue">the nonce that was found in the jwt token.</param>
|
||||
/// <returns>'nonceExpectedValue' if a cookie is found that matches, null otherwise.</returns>
|
||||
/// <remarks>Examine <see cref="HttpRequest.Cookies.Keys"/> that start with the prefix: 'OpenIdConnectAuthenticationDefaults.Nonce'.
|
||||
/// <see cref="OpenIdConnectAuthenticationOptions.StringDataFormat.Unprotect"/> is used to obtain the actual 'nonce'. If the nonce is found, then <see cref="HttpResponse.Cookies.Delete"/> is called.</remarks>
|
||||
private string RetrieveNonce(string nonceExpectedValue)
|
||||
{
|
||||
if (nonceExpectedValue == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var nonceKey in Request.Cookies.Keys)
|
||||
{
|
||||
if (nonceKey.StartsWith(OpenIdConnectAuthenticationDefaults.CookieNoncePrefix))
|
||||
{
|
||||
try
|
||||
{
|
||||
string nonceDecodedValue = Options.StringDataFormat.Unprotect(nonceKey.Substring(OpenIdConnectAuthenticationDefaults.CookieNoncePrefix.Length, nonceKey.Length - OpenIdConnectAuthenticationDefaults.CookieNoncePrefix.Length));
|
||||
if (nonceDecodedValue == nonceExpectedValue)
|
||||
{
|
||||
var cookieOptions = new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = Request.IsSecure
|
||||
};
|
||||
|
||||
Response.Cookies.Delete(nonceKey, cookieOptions);
|
||||
return nonceExpectedValue;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.WriteWarning("Failed to un-protect the nonce cookie.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private AuthenticationProperties GetPropertiesFromState(string state)
|
||||
{
|
||||
// assume a well formed query string: <a=b&>OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey=kasjd;fljasldkjflksdj<&c=d>
|
||||
int startIndex = 0;
|
||||
if (string.IsNullOrWhiteSpace(state) || (startIndex = state.IndexOf(OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey, StringComparison.Ordinal)) == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int authenticationIndex = startIndex + OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey.Length;
|
||||
if (authenticationIndex == -1 || authenticationIndex == state.Length || state[authenticationIndex] != '=')
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// scan rest of string looking for '&'
|
||||
authenticationIndex++;
|
||||
int endIndex = state.Substring(authenticationIndex, state.Length - authenticationIndex).IndexOf("&", StringComparison.Ordinal);
|
||||
|
||||
// -1 => no other parameters are after the AuthenticationPropertiesKey
|
||||
if (endIndex == -1)
|
||||
{
|
||||
return Options.StateDataFormat.Unprotect(Uri.UnescapeDataString(state.Substring(authenticationIndex).Replace('+', ' ')));
|
||||
}
|
||||
else
|
||||
{
|
||||
return Options.StateDataFormat.Unprotect(Uri.UnescapeDataString(state.Substring(authenticationIndex, endIndex).Replace('+', ' ')));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls InvokeReplyPathAsync
|
||||
/// </summary>
|
||||
/// <returns>True if the request was handled, false if the next middleware should be invoked.</returns>
|
||||
public override Task<bool> InvokeAsync()
|
||||
{
|
||||
return InvokeReplyPathAsync();
|
||||
}
|
||||
|
||||
private async Task<bool> InvokeReplyPathAsync()
|
||||
{
|
||||
AuthenticationTicket ticket = await AuthenticateAsync();
|
||||
|
||||
if (ticket != null)
|
||||
{
|
||||
if (ticket.Principal != null)
|
||||
{
|
||||
Request.HttpContext.Response.SignIn(ticket.Properties, ticket.Principal.Identities);
|
||||
}
|
||||
|
||||
// Redirect back to the original secured resource, if any.
|
||||
if (!string.IsNullOrWhiteSpace(ticket.Properties.RedirectUri))
|
||||
{
|
||||
Response.Redirect(ticket.Properties.RedirectUri);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.34014
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.AspNet.Security.OpenIdConnect {
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Owin.Security.OpenIdConnect.Resources", IntrospectionExtensions.GetTypeInfo(typeof(Resources)).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to BackchannelTimeout cannot be less or equal to TimeSpan.Zero..
|
||||
/// </summary>
|
||||
internal static string ArgsException_BackchallelLessThanZero {
|
||||
get {
|
||||
return ResourceManager.GetString("ArgsException_BackchallelLessThanZero", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to "OpenIdConnectMessage.Error was not null, indicating an error. Error: '{0}'. Error_Description (may be empty): '{1}'. Error_Uri (may be empty): '{2}'.".
|
||||
/// </summary>
|
||||
internal static string Exception_OpenIdConnectMessageError {
|
||||
get {
|
||||
return ResourceManager.GetString("Exception_OpenIdConnectMessageError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to OIDC_20001: The query string for Logout is not a well formed URI. The runtime cannot redirect. Redirect uri: '{0}'..
|
||||
/// </summary>
|
||||
internal static string Exception_RedirectUri_LogoutQueryString_IsNotWellFormed {
|
||||
get {
|
||||
return ResourceManager.GetString("Exception_RedirectUri_LogoutQueryString_IsNotWellFormed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a An ICertificateValidator cannot be specified at the same time as an HttpMessageHandler unless it is a WebRequestHandler..
|
||||
/// </summary>
|
||||
internal static string Exception_ValidatorHandlerMismatch {
|
||||
get {
|
||||
return ResourceManager.GetString("Exception_ValidatorHandlerMismatch", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Security": "1.0.0-*",
|
||||
"Microsoft.IdentityModel.Protocol.Extensions": "2.0.0.0-beta1-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": {
|
||||
"frameworkAssemblies": {
|
||||
"System.Net.Http.WebRequest": ""
|
||||
}
|
||||
},
|
||||
"aspnetcore50": {
|
||||
"dependencies": {
|
||||
"System.Net.Http.WinHttpHandler": "4.0.0-beta-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ using System.Security.Cryptography;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Core.Collections;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Microsoft.AspNet.Security.Twitter.Messages;
|
||||
|
|
@ -275,7 +276,7 @@ namespace Microsoft.AspNet.Security.Twitter
|
|||
response.EnsureSuccessStatusCode();
|
||||
string responseText = await response.Content.ReadAsStringAsync();
|
||||
|
||||
IFormCollection responseParameters = FormHelpers.ParseForm(responseText);
|
||||
IFormCollection responseParameters = new FormCollection(FormReader.ReadForm(responseText));
|
||||
if (string.Equals(responseParameters["oauth_callback_confirmed"], "true", StringComparison.Ordinal))
|
||||
{
|
||||
return new RequestToken { Token = Uri.UnescapeDataString(responseParameters["oauth_token"]), TokenSecret = Uri.UnescapeDataString(responseParameters["oauth_token_secret"]), CallbackConfirmed = true, Properties = properties };
|
||||
|
|
@ -351,7 +352,7 @@ namespace Microsoft.AspNet.Security.Twitter
|
|||
|
||||
string responseText = await response.Content.ReadAsStringAsync();
|
||||
|
||||
IFormCollection responseParameters = FormHelpers.ParseForm(responseText);
|
||||
IFormCollection responseParameters = new FormCollection(FormReader.ReadForm(responseText));
|
||||
|
||||
return new AccessToken
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.HttpFeature.Security;
|
||||
using Microsoft.AspNet.PipelineCore.Security;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,14 +1,8 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.HttpFeature.Security;
|
||||
using Microsoft.AspNet.PipelineCore.Security;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
|
|
@ -28,11 +22,34 @@ namespace Microsoft.AspNet.Security
|
|||
Properties = properties ?? new AuthenticationProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuthenticationTicket"/> class
|
||||
/// </summary>
|
||||
/// <param name="identity">the <see cref="ClaimsPrincipal"/> that represents the authenticated user.</param>
|
||||
/// <param name="properties">additional properties that can be consumed by the user or runtime.</param>
|
||||
/// <param name="authenticationType">the authentication middleware that was responsible for this ticket.</param>
|
||||
public AuthenticationTicket(ClaimsPrincipal principal, AuthenticationProperties properties, string authenticationType)
|
||||
{
|
||||
AuthenticationType = authenticationType;
|
||||
Principal = principal;
|
||||
Properties = properties ?? new AuthenticationProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication type.
|
||||
/// </summary>
|
||||
public string AuthenticationType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authenticated user identity.
|
||||
/// </summary>
|
||||
public ClaimsIdentity Identity { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the claims-principal with authenticated user identities.
|
||||
/// </summary>
|
||||
public ClaimsPrincipal Principal{ get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional state values for the authentication session.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains authorization information used by <see cref="IAuthorizationPolicyHandler"/>.
|
||||
/// </summary>
|
||||
public class AuthorizationContext
|
||||
{
|
||||
private HashSet<IAuthorizationRequirement> _pendingRequirements = new HashSet<IAuthorizationRequirement>();
|
||||
private bool _failCalled;
|
||||
private bool _succeedCalled;
|
||||
|
||||
public AuthorizationContext(
|
||||
[NotNull] AuthorizationPolicy policy,
|
||||
HttpContext context,
|
||||
object resource)
|
||||
{
|
||||
Policy = policy;
|
||||
Context = context;
|
||||
Resource = resource;
|
||||
foreach (var req in Policy.Requirements)
|
||||
{
|
||||
_pendingRequirements.Add(req);
|
||||
}
|
||||
}
|
||||
|
||||
public AuthorizationPolicy Policy { get; private set; }
|
||||
public ClaimsPrincipal User { get { return Context.User; } }
|
||||
public HttpContext Context { get; private set; }
|
||||
public object Resource { get; private set; }
|
||||
|
||||
public IEnumerable<IAuthorizationRequirement> PendingRequirements { get { return _pendingRequirements; } }
|
||||
|
||||
public bool HasFailed { get { return _failCalled; } }
|
||||
|
||||
public bool HasSucceeded {
|
||||
get
|
||||
{
|
||||
return !_failCalled && _succeedCalled && !PendingRequirements.Any();
|
||||
}
|
||||
}
|
||||
|
||||
public void Fail()
|
||||
{
|
||||
_failCalled = true;
|
||||
}
|
||||
|
||||
public void Succeed(IAuthorizationRequirement requirement)
|
||||
{
|
||||
_succeedCalled = true;
|
||||
_pendingRequirements.Remove(requirement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
// Music store use case
|
||||
|
||||
// await AuthorizeAsync<Album>(user, "Edit", albumInstance);
|
||||
|
||||
// No policy name needed because this is auto based on resource (operation is the policy name)
|
||||
//RegisterOperation which auto generates the policy for Authorize<T>
|
||||
//bool AuthorizeAsync<TResource>(ClaimsPrincipal, string operation, TResource instance)
|
||||
//bool AuthorizeAsync<TResource>(IAuthorization, ClaimsPrincipal, string operation, TResource instance)
|
||||
public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler
|
||||
where TRequirement : IAuthorizationRequirement
|
||||
{
|
||||
public async Task HandleAsync(AuthorizationContext context)
|
||||
{
|
||||
foreach (var req in context.Policy.Requirements.OfType<TRequirement>())
|
||||
{
|
||||
if (await CheckAsync(context, req))
|
||||
{
|
||||
context.Succeed(req);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Task<bool> CheckAsync(AuthorizationContext context, TRequirement requirement);
|
||||
}
|
||||
|
||||
// TODO:
|
||||
//public abstract class AuthorizationHandler<TRequirement, TResource> : AuthorizationHandler<TRequirement>
|
||||
// where TResource : class
|
||||
// where TRequirement : IAuthorizationRequirement
|
||||
//{
|
||||
// public override Task HandleAsync(AuthorizationContext context)
|
||||
// {
|
||||
// var resource = context.Resource as TResource;
|
||||
// if (resource != null)
|
||||
// {
|
||||
// return HandleAsync(context, resource);
|
||||
// }
|
||||
|
||||
// return Task.FromResult(0);
|
||||
|
||||
// }
|
||||
|
||||
// public abstract Task HandleAsync(AuthorizationContext context, TResource resource);
|
||||
//}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public class AuthorizationOptions
|
||||
{
|
||||
// TODO: make this case insensitive
|
||||
private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>();
|
||||
|
||||
public void AddPolicy([NotNull] string name, [NotNull] AuthorizationPolicy policy)
|
||||
{
|
||||
PolicyMap[name] = policy;
|
||||
}
|
||||
|
||||
public void AddPolicy([NotNull] string name, [NotNull] Action<AuthorizationPolicyBuilder> configurePolicy)
|
||||
{
|
||||
var policyBuilder = new AuthorizationPolicyBuilder();
|
||||
configurePolicy(policyBuilder);
|
||||
PolicyMap[name] = policyBuilder.Build();
|
||||
}
|
||||
|
||||
public AuthorizationPolicy GetPolicy([NotNull] string name)
|
||||
{
|
||||
return PolicyMap.ContainsKey(name) ? PolicyMap[name] : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,31 +2,18 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// This class provides a base implementation for <see cref="IAuthorizationPolicy" />
|
||||
/// </summary>
|
||||
public abstract class AuthorizationPolicy : IAuthorizationPolicy
|
||||
public class AuthorizationPolicy
|
||||
{
|
||||
public int Order { get; set; }
|
||||
|
||||
public virtual Task ApplyingAsync(AuthorizationPolicyContext context)
|
||||
public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement> requirements, IEnumerable<string> activeAuthenticationTypes)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
Requirements = requirements;
|
||||
ActiveAuthenticationTypes = activeAuthenticationTypes;
|
||||
}
|
||||
|
||||
public virtual Task ApplyAsync(AuthorizationPolicyContext context)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public virtual Task AppliedAsync(AuthorizationPolicyContext context)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
public IEnumerable<IAuthorizationRequirement> Requirements { get; private set; }
|
||||
public IEnumerable<string> ActiveAuthenticationTypes { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public class AuthorizationPolicyBuilder
|
||||
{
|
||||
public AuthorizationPolicyBuilder(params string[] activeAuthenticationTypes)
|
||||
{
|
||||
AddAuthenticationTypes(activeAuthenticationTypes);
|
||||
}
|
||||
|
||||
public AuthorizationPolicyBuilder(AuthorizationPolicy policy)
|
||||
{
|
||||
Combine(policy);
|
||||
}
|
||||
|
||||
public IList<IAuthorizationRequirement> Requirements { get; set; } = new List<IAuthorizationRequirement>();
|
||||
public IList<string> ActiveAuthenticationTypes { get; set; } = new List<string>();
|
||||
|
||||
public AuthorizationPolicyBuilder AddAuthenticationTypes(params string[] activeAuthTypes)
|
||||
{
|
||||
foreach (var authType in activeAuthTypes)
|
||||
{
|
||||
ActiveAuthenticationTypes.Add(authType);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuthorizationPolicyBuilder AddRequirements(params IAuthorizationRequirement[] requirements)
|
||||
{
|
||||
foreach (var req in requirements)
|
||||
{
|
||||
Requirements.Add(req);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuthorizationPolicyBuilder Combine(AuthorizationPolicy policy)
|
||||
{
|
||||
AddAuthenticationTypes(policy.ActiveAuthenticationTypes.ToArray());
|
||||
AddRequirements(policy.Requirements.ToArray());
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuthorizationPolicyBuilder RequiresClaim([NotNull] string claimType, params string[] requiredValues)
|
||||
{
|
||||
Requirements.Add(new ClaimsAuthorizationRequirement
|
||||
{
|
||||
ClaimType = claimType,
|
||||
AllowedValues = requiredValues
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuthorizationPolicyBuilder RequiresClaim([NotNull] string claimType)
|
||||
{
|
||||
Requirements.Add(new ClaimsAuthorizationRequirement
|
||||
{
|
||||
ClaimType = claimType,
|
||||
AllowedValues = null
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuthorizationPolicyBuilder RequiresRole([NotNull] params string[] roles)
|
||||
{
|
||||
RequiresClaim(ClaimTypes.Role, roles);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuthorizationPolicyBuilder RequireAuthenticatedUser()
|
||||
{
|
||||
Requirements.Add(new DenyAnonymousAuthorizationRequirement());
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuthorizationPolicy Build()
|
||||
{
|
||||
return new AuthorizationPolicy(Requirements, ActiveAuthenticationTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains authorization information used by <see cref="IAuthorizationPolicy"/>.
|
||||
/// </summary>
|
||||
public class AuthorizationPolicyContext
|
||||
{
|
||||
public AuthorizationPolicyContext(IEnumerable<Claim> claims, ClaimsPrincipal user, object resource )
|
||||
{
|
||||
Claims = (claims ?? Enumerable.Empty<Claim>()).ToList();
|
||||
User = user;
|
||||
Resource = resource;
|
||||
|
||||
// user claims are copied to a new and mutable list
|
||||
UserClaims = user != null
|
||||
? user.Claims.ToList()
|
||||
: new List<Claim>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of claims the <see cref="IAuthorizationService"/> is checking.
|
||||
/// </summary>
|
||||
public IList<Claim> Claims { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user to check the claims against.
|
||||
/// </summary>
|
||||
public ClaimsPrincipal User { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The claims of the user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This list can be modified by policies for retries.
|
||||
/// </remarks>
|
||||
public IList<Claim> UserClaims { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional resource associated to the check.
|
||||
/// </summary>
|
||||
public object Resource { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or set whether the permission will be granted to the user.
|
||||
/// </summary>
|
||||
public bool Authorized { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When set to <value>true</value>, the authorization check will be processed again.
|
||||
/// </summary>
|
||||
public bool Retry { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public static class AuthorizationServiceExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if a user has specific claims.
|
||||
/// </summary>
|
||||
/// <param name="claim">The claim to check against a specific user.</param>
|
||||
/// <param name="user">The user to check claims against.</param>
|
||||
/// <returns><value>true</value> when the user fulfills one of the claims, <value>false</value> otherwise.</returns>
|
||||
public static Task<bool> AuthorizeAsync([NotNull] this IAuthorizationService service, Claim claim, ClaimsPrincipal user)
|
||||
{
|
||||
return service.AuthorizeAsync(new Claim[] { claim }, user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a user has specific claims.
|
||||
/// </summary>
|
||||
/// <param name="claim">The claim to check against a specific user.</param>
|
||||
/// <param name="user">The user to check claims against.</param>
|
||||
/// <returns><value>true</value> when the user fulfills one of the claims, <value>false</value> otherwise.</returns>
|
||||
public static bool Authorize([NotNull] this IAuthorizationService service, Claim claim, ClaimsPrincipal user)
|
||||
{
|
||||
return service.Authorize(new Claim[] { claim }, user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a user has specific claims for a specific context obj.
|
||||
/// </summary>
|
||||
/// <param name="claim">The claim to check against a specific user.</param>
|
||||
/// <param name="user">The user to check claims against.</param>
|
||||
/// <param name="resource">The resource the claims should be check with.</param>
|
||||
/// <returns><value>true</value> when the user fulfills one of the claims, <value>false</value> otherwise.</returns>
|
||||
public static Task<bool> AuthorizeAsync([NotNull] this IAuthorizationService service, Claim claim, ClaimsPrincipal user, object resource)
|
||||
{
|
||||
return service.AuthorizeAsync(new Claim[] { claim }, user, resource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a user has specific claims for a specific context obj.
|
||||
/// </summary>
|
||||
/// <param name="claim">The claimsto check against a specific user.</param>
|
||||
/// <param name="user">The user to check claims against.</param>
|
||||
/// <param name="resource">The resource the claims should be check with.</param>
|
||||
/// <returns><value>true</value> when the user fulfills one of the claims, <value>false</value> otherwise.</returns>
|
||||
public static bool Authorize([NotNull] this IAuthorizationService service, Claim claim, ClaimsPrincipal user, object resource)
|
||||
{
|
||||
return service.Authorize(new Claim[] { claim }, user, resource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a user has specific claims.
|
||||
/// </summary>
|
||||
/// <param name="claims">The claims to check against a specific user.</param>
|
||||
/// <param name="user">The user to check claims against.</param>
|
||||
/// <returns><value>true</value> when the user fulfills one of the claims, <value>false</value> otherwise.</returns>
|
||||
public static Task<bool> AuthorizeAsync([NotNull] this IAuthorizationService service, IEnumerable<Claim> claims, ClaimsPrincipal user)
|
||||
{
|
||||
return service.AuthorizeAsync(claims, user, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a user has specific claims.
|
||||
/// </summary>
|
||||
/// <param name="claims">The claims to check against a specific user.</param>
|
||||
/// <param name="user">The user to check claims against.</param>
|
||||
/// <returns><value>true</value> when the user fulfills one of the claims, <value>false</value> otherwise.</returns>
|
||||
public static bool Authorize([NotNull] this IAuthorizationService service, IEnumerable<Claim> claims, ClaimsPrincipal user)
|
||||
{
|
||||
return service.Authorize(claims, user, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public class ClaimsAuthorizationHandler : AuthorizationHandler<ClaimsAuthorizationRequirement>
|
||||
{
|
||||
public override Task<bool> CheckAsync(AuthorizationContext context, ClaimsAuthorizationRequirement requirement)
|
||||
{
|
||||
if (context.Context.User == null)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
if (requirement.AllowedValues == null || !requirement.AllowedValues.Any())
|
||||
{
|
||||
found = context.Context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
else
|
||||
{
|
||||
found = context.Context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase)
|
||||
&& requirement.AllowedValues.Contains(c.Value, StringComparer.Ordinal));
|
||||
}
|
||||
return Task.FromResult(found);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
// Must contain a claim with the specified name, and at least one of the required values
|
||||
// If AllowedValues is null or empty, that means any claim is valid
|
||||
public class ClaimsAuthorizationRequirement : IAuthorizationRequirement
|
||||
{
|
||||
public string ClaimType { get; set; }
|
||||
public IEnumerable<string> AllowedValues { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,101 +1,67 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public class DefaultAuthorizationService : IAuthorizationService
|
||||
{
|
||||
private readonly IList<IAuthorizationPolicy> _policies;
|
||||
public int MaxRetries = 99;
|
||||
private readonly IList<IAuthorizationHandler> _handlers;
|
||||
private readonly AuthorizationOptions _options;
|
||||
|
||||
public DefaultAuthorizationService(IEnumerable<IAuthorizationPolicy> policies)
|
||||
public DefaultAuthorizationService(IOptions<AuthorizationOptions> options, IEnumerable<IAuthorizationHandler> handlers)
|
||||
{
|
||||
if (policies == null)
|
||||
{
|
||||
_policies = Enumerable.Empty<IAuthorizationPolicy>().ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
_policies = policies.OrderBy(x => x.Order).ToArray();
|
||||
}
|
||||
_handlers = handlers.ToArray();
|
||||
_options = options.Options;
|
||||
}
|
||||
|
||||
public async Task<bool> AuthorizeAsync(IEnumerable<Claim> claims, ClaimsPrincipal user, object resource)
|
||||
public Task<bool> AuthorizeAsync([NotNull] string policyName, HttpContext context, object resource = null)
|
||||
{
|
||||
var context = new AuthorizationPolicyContext(claims, user, resource);
|
||||
|
||||
foreach (var policy in _policies)
|
||||
var policy = _options.GetPolicy(policyName);
|
||||
if (policy == null)
|
||||
{
|
||||
await policy.ApplyingAsync(context);
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
return AuthorizeAsync(policy, context, resource);
|
||||
}
|
||||
|
||||
// we only apply the policies for a limited number of times to prevent
|
||||
// infinite loops
|
||||
|
||||
int retries;
|
||||
for (retries = 0; retries < MaxRetries; retries++)
|
||||
public async Task<bool> AuthorizeAsync([NotNull] AuthorizationPolicy policy, [NotNull] HttpContext context, object resource = null)
|
||||
{
|
||||
var user = context.User;
|
||||
try
|
||||
{
|
||||
// we don't need to check for owned claims if the permission is already granted
|
||||
if (!context.Authorized)
|
||||
// Generate the user identities if policy specified the AuthTypes
|
||||
if (policy.ActiveAuthenticationTypes != null && policy.ActiveAuthenticationTypes.Any() )
|
||||
{
|
||||
if (context.User != null)
|
||||
var principal = new ClaimsPrincipal();
|
||||
|
||||
var results = await context.AuthenticateAsync(policy.ActiveAuthenticationTypes);
|
||||
// REVIEW: re requesting the identities fails for MVC currently, so we only request if not found
|
||||
foreach (var result in results)
|
||||
{
|
||||
if (ClaimsMatch(context.Claims, context.UserClaims))
|
||||
{
|
||||
context.Authorized = true;
|
||||
}
|
||||
principal.AddIdentity(result.Identity);
|
||||
}
|
||||
context.User = principal;
|
||||
}
|
||||
|
||||
// reset the retry flag
|
||||
context.Retry = false;
|
||||
var authContext = new AuthorizationContext(policy, context, resource);
|
||||
|
||||
// give a chance for policies to change claims or the grant
|
||||
foreach (var policy in _policies)
|
||||
foreach (var handler in _handlers)
|
||||
{
|
||||
await policy.ApplyAsync(context);
|
||||
}
|
||||
|
||||
// if no policies have changed the context, stop checking
|
||||
if (!context.Retry)
|
||||
{
|
||||
break;
|
||||
await handler.HandleAsync(authContext);
|
||||
}
|
||||
return authContext.HasSucceeded;
|
||||
}
|
||||
|
||||
if (retries == MaxRetries)
|
||||
finally
|
||||
{
|
||||
throw new InvalidOperationException("Too many authorization retries.");
|
||||
context.User = user;
|
||||
}
|
||||
|
||||
foreach (var policy in _policies)
|
||||
{
|
||||
await policy.AppliedAsync(context);
|
||||
}
|
||||
|
||||
return context.Authorized;
|
||||
}
|
||||
|
||||
public bool Authorize(IEnumerable<Claim> claims, ClaimsPrincipal user, object resource)
|
||||
{
|
||||
return AuthorizeAsync(claims, user, resource).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private bool ClaimsMatch([NotNull] IEnumerable<Claim> x, [NotNull] IEnumerable<Claim> y)
|
||||
{
|
||||
return x.Any(claim =>
|
||||
y.Any(userClaim =>
|
||||
string.Equals(claim.Type, userClaim.Type, StringComparison.OrdinalIgnoreCase) &&
|
||||
string.Equals(claim.Value, userClaim.Value, StringComparison.Ordinal)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public class DenyAnonymousAuthorizationHandler : AuthorizationHandler<DenyAnonymousAuthorizationRequirement>
|
||||
{
|
||||
public override Task<bool> CheckAsync(AuthorizationContext context, DenyAnonymousAuthorizationRequirement requirement)
|
||||
{
|
||||
var user = context.User;
|
||||
var userIsAnonymous =
|
||||
user == null ||
|
||||
user.Identity == null ||
|
||||
!user.Identity.IsAuthenticated;
|
||||
return Task.FromResult(!userIsAnonymous);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Security;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public class DenyAnonymousAuthorizationRequirement : IAuthorizationRequirement { }
|
||||
}
|
||||
|
|
@ -5,11 +5,9 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public interface IAuthorizationPolicy
|
||||
public interface IAuthorizationHandler
|
||||
{
|
||||
int Order { get; set; }
|
||||
Task ApplyingAsync(AuthorizationPolicyContext context);
|
||||
Task ApplyAsync(AuthorizationPolicyContext context);
|
||||
Task AppliedAsync(AuthorizationPolicyContext context);
|
||||
Task HandleAsync(AuthorizationContext context);
|
||||
//void Handle(AuthorizationContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public interface IAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +1,32 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks claims based permissions for a user.
|
||||
/// Checks policy based permissions for a user
|
||||
/// </summary>
|
||||
public interface IAuthorizationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if a user has specific claims for a specific context obj.
|
||||
/// Checks if a user meets a specific authorization policy
|
||||
/// </summary>
|
||||
/// <param name="claims">The claims to check against a specific user.</param>
|
||||
/// <param name="user">The user to check claims against.</param>
|
||||
/// <param name="resource">The resource the claims should be check with.</param>
|
||||
/// <returns><value>true</value> when the user fulfills one of the claims, <value>false</value> otherwise.</returns>
|
||||
Task<bool> AuthorizeAsync(IEnumerable<Claim> claims, ClaimsPrincipal user, object resource);
|
||||
/// <param name="policy">The policy to check against a specific context.</param>
|
||||
/// <param name="context">The HttpContext to check the policy against.</param>
|
||||
/// <param name="resource">The resource the policy should be checked with.</param>
|
||||
/// <returns><value>true</value> when the user fulfills the policy, <value>false</value> otherwise.</returns>
|
||||
Task<bool> AuthorizeAsync(string policyName, HttpContext context, object resource = null);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a user has specific claims for a specific context obj.
|
||||
/// Checks if a user meets a specific authorization policy
|
||||
/// </summary>
|
||||
/// <param name="claims">The claims to check against a specific user.</param>
|
||||
/// <param name="user">The user to check claims against.</param>
|
||||
/// <param name="resource">The resource the claims should be check with.</param>
|
||||
/// <returns><value>true</value> when the user fulfills one of the claims, <value>false</value> otherwise.</returns>
|
||||
bool Authorize(IEnumerable<Claim> claims, ClaimsPrincipal user, object resource);
|
||||
|
||||
/// <param name="policy">The policy to check against a specific context.</param>
|
||||
/// <param name="context">The HttpContext to check the policy against.</param>
|
||||
/// <param name="resource">The resource the policy should be checked with.</param>
|
||||
/// <returns><value>true</value> when the user fulfills the policy, <value>false</value> otherwise.</returns>
|
||||
Task<bool> AuthorizeAsync(AuthorizationPolicy policy, HttpContext context, object resource = null);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.HttpFeature.Security;
|
||||
using Microsoft.AspNet.Http.Interfaces.Security;
|
||||
using Microsoft.AspNet.Security.DataHandler.Encoder;
|
||||
using Microsoft.Framework.Logging;
|
||||
|
||||
|
|
@ -60,6 +60,8 @@ namespace Microsoft.AspNet.Security.Infrastructure
|
|||
|
||||
public IAuthenticationHandler PriorHandler { get; set; }
|
||||
|
||||
public bool Faulted { get; set; }
|
||||
|
||||
protected async Task BaseInitializeAsync(AuthenticationOptions options, HttpContext context)
|
||||
{
|
||||
_baseOptions = options;
|
||||
|
|
@ -94,12 +96,29 @@ namespace Microsoft.AspNet.Security.Infrastructure
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once per request after Initialize and Invoke.
|
||||
/// Called once per request after Initialize and Invoke.
|
||||
/// </summary>
|
||||
/// <returns>async completion</returns>
|
||||
internal async Task TeardownAsync()
|
||||
{
|
||||
await ApplyResponseAsync();
|
||||
try
|
||||
{
|
||||
await ApplyResponseAsync();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
await TeardownCoreAsync();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Don't mask the original exception
|
||||
}
|
||||
UnregisterAuthenticationHandler();
|
||||
throw;
|
||||
}
|
||||
|
||||
await TeardownCoreAsync();
|
||||
UnregisterAuthenticationHandler();
|
||||
}
|
||||
|
|
@ -217,15 +236,29 @@ namespace Microsoft.AspNet.Security.Infrastructure
|
|||
|
||||
private void ApplyResponse()
|
||||
{
|
||||
LazyInitializer.EnsureInitialized(
|
||||
ref _applyResponse,
|
||||
ref _applyResponseInitialized,
|
||||
ref _applyResponseSyncLock,
|
||||
() =>
|
||||
// If ApplyResponse already failed in the OnSendingHeaderCallback or TeardownAsync code path then a
|
||||
// failed task is cached. If called again the same error will be re-thrown. This breaks error handling
|
||||
// scenarios like the ability to display the error page or re-execute the request.
|
||||
try
|
||||
{
|
||||
if (!Faulted)
|
||||
{
|
||||
ApplyResponseCore();
|
||||
return Task.FromResult(0);
|
||||
}).GetAwaiter().GetResult(); // Block if the async version is in progress.
|
||||
LazyInitializer.EnsureInitialized(
|
||||
ref _applyResponse,
|
||||
ref _applyResponseInitialized,
|
||||
ref _applyResponseSyncLock,
|
||||
() =>
|
||||
{
|
||||
ApplyResponseCore();
|
||||
return Task.FromResult(0);
|
||||
}).GetAwaiter().GetResult(); // Block if the async version is in progress.
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Faulted = true;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ApplyResponseCore()
|
||||
|
|
@ -240,13 +273,27 @@ namespace Microsoft.AspNet.Security.Infrastructure
|
|||
/// or later, as the last step when the original async call to the middleware is returning.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private Task ApplyResponseAsync()
|
||||
private async Task ApplyResponseAsync()
|
||||
{
|
||||
return LazyInitializer.EnsureInitialized(
|
||||
ref _applyResponse,
|
||||
ref _applyResponseInitialized,
|
||||
ref _applyResponseSyncLock,
|
||||
ApplyResponseCoreAsync);
|
||||
// If ApplyResponse already failed in the OnSendingHeaderCallback or TeardownAsync code path then a
|
||||
// failed task is cached. If called again the same error will be re-thrown. This breaks error handling
|
||||
// scenarios like the ability to display the error page or re-execute the request.
|
||||
try
|
||||
{
|
||||
if (!Faulted)
|
||||
{
|
||||
await LazyInitializer.EnsureInitialized(
|
||||
ref _applyResponse,
|
||||
ref _applyResponseInitialized,
|
||||
ref _applyResponseSyncLock,
|
||||
ApplyResponseCoreAsync);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Faulted = true;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -41,9 +41,25 @@ namespace Microsoft.AspNet.Security.Infrastructure
|
|||
{
|
||||
AuthenticationHandler<TOptions> handler = CreateHandler();
|
||||
await handler.Initialize(Options, context);
|
||||
if (!await handler.InvokeAsync())
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
if (!await handler.InvokeAsync())
|
||||
{
|
||||
await _next(context);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler.Faulted = true;
|
||||
await handler.TeardownAsync();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Don't mask the original exception
|
||||
}
|
||||
throw;
|
||||
}
|
||||
await handler.TeardownAsync();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Security.Notifications;
|
||||
|
||||
|
|
@ -10,15 +8,11 @@ namespace Microsoft.AspNet.Security.Infrastructure
|
|||
{
|
||||
public class AuthenticationTokenReceiveContext : BaseContext
|
||||
{
|
||||
private readonly ISecureDataFormat<AuthenticationTicket> _secureDataFormat;
|
||||
|
||||
public AuthenticationTokenReceiveContext(
|
||||
[NotNull] HttpContext context,
|
||||
[NotNull] ISecureDataFormat<AuthenticationTicket> secureDataFormat,
|
||||
[NotNull] string token)
|
||||
: base(context)
|
||||
{
|
||||
_secureDataFormat = secureDataFormat;
|
||||
Token = token;
|
||||
}
|
||||
|
||||
|
|
@ -26,11 +20,6 @@ namespace Microsoft.AspNet.Security.Infrastructure
|
|||
|
||||
public AuthenticationTicket Ticket { get; protected set; }
|
||||
|
||||
public void DeserializeTicket(string protectedData)
|
||||
{
|
||||
Ticket = _secureDataFormat.Unprotect(protectedData);
|
||||
}
|
||||
|
||||
public void SetTicket([NotNull] AuthenticationTicket ticket)
|
||||
{
|
||||
Ticket = ticket;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.HttpFeature.Security;
|
||||
using Microsoft.AspNet.PipelineCore.Security;
|
||||
using Microsoft.AspNet.Http.Core.Security;
|
||||
using Microsoft.AspNet.Http.Interfaces.Security;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Infrastructure
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public class AuthenticationFailedNotification<TMessage>
|
||||
public class AuthenticationFailedNotification<TMessage, TOptions> : BaseNotification<TOptions>
|
||||
{
|
||||
public AuthenticationFailedNotification()
|
||||
public AuthenticationFailedNotification(HttpContext context, TOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public bool Cancel { get; set; }
|
||||
public Exception Exception { get; set; }
|
||||
|
||||
public TMessage ProtocolMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public class BaseNotification<TOptions> : BaseContext<TOptions>
|
||||
{
|
||||
protected BaseNotification(HttpContext context, TOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public NotificationResultState State { get; set; }
|
||||
|
||||
public bool HandledResponse
|
||||
{
|
||||
get { return State == NotificationResultState.HandledResponse; }
|
||||
}
|
||||
|
||||
public bool Skipped
|
||||
{
|
||||
get { return State == NotificationResultState.Skipped; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discontinue all processing for this request and return to the client.
|
||||
/// The caller is responsible for generating the full response.
|
||||
/// Set the <see cref="AuthenticationTicket"/> to trigger SignIn.
|
||||
/// </summary>
|
||||
public void HandleResponse()
|
||||
{
|
||||
State = NotificationResultState.HandledResponse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discontinue processing the request in the current middleware and pass control to the next one.
|
||||
/// SignIn will not be called.
|
||||
/// </summary>
|
||||
public void SkipToNextMiddleware()
|
||||
{
|
||||
State = NotificationResultState.Skipped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or set the <see cref="AuthenticationTicket"/> to return if this notification signals it handled the notification.
|
||||
/// </summary>
|
||||
public AuthenticationTicket AuthenticationTicket { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,21 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public class MessageReceivedNotification<TMessage>
|
||||
public class MessageReceivedNotification<TMessage, TOptions> : BaseNotification<TOptions>
|
||||
{
|
||||
public MessageReceivedNotification()
|
||||
public MessageReceivedNotification(HttpContext context, TOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public bool Cancel { get; set; }
|
||||
public TMessage ProtocolMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bearer Token. This will give application an opportunity to retrieve token from an alternation location.
|
||||
/// </summary>
|
||||
public string Token { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public enum NotificationResultState
|
||||
{
|
||||
/// <summary>
|
||||
/// Continue with normal processing.
|
||||
/// </summary>
|
||||
Continue,
|
||||
|
||||
/// <summary>
|
||||
/// Discontinue processing the request in the current middleware and pass control to the next one.
|
||||
/// </summary>
|
||||
Skipped,
|
||||
|
||||
/// <summary>
|
||||
/// Discontinue all processing for this request.
|
||||
/// </summary>
|
||||
HandledResponse
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,21 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public class RedirectFromIdentityProviderNotification
|
||||
public class RedirectFromIdentityProviderNotification<TMessage, TOptions> : BaseNotification<TOptions>
|
||||
{
|
||||
public AuthenticationTicket AuthenticationTicket { get; set; }
|
||||
public RedirectFromIdentityProviderNotification(HttpContext context, TOptions options)
|
||||
: base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public string SignInAsAuthenticationType { get; set; }
|
||||
|
||||
public bool Cancel { get; set; }
|
||||
|
||||
public bool IsRequestCompleted { get; set; }
|
||||
|
||||
public TMessage ProtocolMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public class RedirectToIdentityProviderNotification<TMessage>
|
||||
public class RedirectToIdentityProviderNotification<TMessage, TOptions> : BaseNotification<TOptions>
|
||||
{
|
||||
public RedirectToIdentityProviderNotification()
|
||||
public RedirectToIdentityProviderNotification(HttpContext context, TOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public bool Cancel { get; set; }
|
||||
public TMessage ProtocolMessage { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public class SecurityTokenReceivedNotification
|
||||
public class SecurityTokenReceivedNotification<TMessage, TOptions> : BaseNotification<TOptions>
|
||||
{
|
||||
public SecurityTokenReceivedNotification()
|
||||
public SecurityTokenReceivedNotification(HttpContext context, TOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public bool Cancel { get; set; }
|
||||
public string SecurityToken { get; set; }
|
||||
|
||||
public TMessage ProtocolMessage { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Notifications
|
||||
{
|
||||
public class SecurityTokenValidatedNotification
|
||||
public class SecurityTokenValidatedNotification<TMessage, TOptions> : BaseNotification<TOptions>
|
||||
{
|
||||
public SecurityTokenValidatedNotification()
|
||||
public SecurityTokenValidatedNotification(HttpContext context, TOptions options) : base(context, options)
|
||||
{
|
||||
}
|
||||
|
||||
public AuthenticationTicket AuthenticationTicket { get; set; }
|
||||
public bool Cancel { get; set; }
|
||||
public TMessage ProtocolMessage { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public class PassThroughAuthorizationHandler : IAuthorizationHandler
|
||||
{
|
||||
public async Task HandleAsync(AuthorizationContext context)
|
||||
{
|
||||
foreach (var handler in context.Policy.Requirements.OfType<IAuthorizationHandler>())
|
||||
{
|
||||
await handler.HandleAsync(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Security;
|
||||
using Microsoft.Framework.ConfigurationModel;
|
||||
|
||||
namespace Microsoft.Framework.DependencyInjection
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection ConfigureAuthorization([NotNull] this IServiceCollection services, [NotNull] Action<AuthorizationOptions> configure)
|
||||
{
|
||||
return services.Configure(configure);
|
||||
}
|
||||
|
||||
// Review: Need UseDefaultSubkey parameter?
|
||||
public static IServiceCollection AddAuthorization([NotNull] this IServiceCollection services, IConfiguration config = null, Action<AuthorizationOptions> configureOptions = null)
|
||||
{
|
||||
var describe = new ServiceDescriber(config);
|
||||
services.AddOptions(config);
|
||||
services.TryAdd(describe.Transient<IAuthorizationService, DefaultAuthorizationService>());
|
||||
services.Add(describe.Transient<IAuthorizationHandler, ClaimsAuthorizationHandler>());
|
||||
services.Add(describe.Transient<IAuthorizationHandler, DenyAnonymousAuthorizationHandler>());
|
||||
services.Add(describe.Transient<IAuthorizationHandler, PassThroughAuthorizationHandler>());
|
||||
if (configureOptions != null)
|
||||
{
|
||||
services.Configure(configureOptions);
|
||||
}
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@
|
|||
"description": "ASP.NET 5 common types used by the various authentication middleware.",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.RequestContainer": "1.0.0-*",
|
||||
"Microsoft.AspNet.HttpFeature": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.AspNet.PipelineCore": "1.0.0-*",
|
||||
"Microsoft.AspNet.Http.Interfaces": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.AspNet.Http.Core": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.DataProtection": "1.0.0-*",
|
||||
"Microsoft.Framework.Logging": "1.0.0-*"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -137,21 +137,11 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
options.CookieDomain = "another.com";
|
||||
options.CookieSecure = CookieSecureOption.Always;
|
||||
options.CookieHttpOnly = true;
|
||||
}, SignInAsAlice);
|
||||
}, SignInAsAlice, new Uri("http://example.com/base"));
|
||||
|
||||
Transaction transaction1 = await SendAsync(server1, "http://example.com/testpath");
|
||||
|
||||
TestServer server2 = CreateServer(options =>
|
||||
{
|
||||
options.CookieName = "SecondCookie";
|
||||
options.CookieSecure = CookieSecureOption.Never;
|
||||
options.CookieHttpOnly = false;
|
||||
}, SignInAsAlice);
|
||||
|
||||
Transaction transaction2 = await SendAsync(server2, "http://example.com/testpath");
|
||||
Transaction transaction1 = await SendAsync(server1, "http://example.com/base/testpath");
|
||||
|
||||
string setCookie1 = transaction1.SetCookie;
|
||||
string setCookie2 = transaction2.SetCookie;
|
||||
|
||||
setCookie1.ShouldContain("TestCookie=");
|
||||
setCookie1.ShouldContain(" path=/foo");
|
||||
|
|
@ -159,7 +149,19 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
setCookie1.ShouldContain(" secure");
|
||||
setCookie1.ShouldContain(" HttpOnly");
|
||||
|
||||
TestServer server2 = CreateServer(options =>
|
||||
{
|
||||
options.CookieName = "SecondCookie";
|
||||
options.CookieSecure = CookieSecureOption.Never;
|
||||
options.CookieHttpOnly = false;
|
||||
}, SignInAsAlice, new Uri("http://example.com/base"));
|
||||
|
||||
Transaction transaction2 = await SendAsync(server2, "http://example.com/base/testpath");
|
||||
|
||||
string setCookie2 = transaction2.SetCookie;
|
||||
|
||||
setCookie2.ShouldContain("SecondCookie=");
|
||||
setCookie2.ShouldContain(" path=/base");
|
||||
setCookie2.ShouldNotContain(" domain=");
|
||||
setCookie2.ShouldNotContain(" secure");
|
||||
setCookie2.ShouldNotContain(" HttpOnly");
|
||||
|
|
@ -343,6 +345,25 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
responded.Single().ShouldContain("\"location\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CookieUsesPathBaseByDefault()
|
||||
{
|
||||
var clock = new TestClock();
|
||||
TestServer server = CreateServer(options =>
|
||||
{
|
||||
},
|
||||
context =>
|
||||
{
|
||||
Assert.Equal(new PathString("/base"), context.Request.PathBase);
|
||||
context.Response.SignIn(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies")));
|
||||
return Task.FromResult<object>(null);
|
||||
},
|
||||
new Uri("http://example.com/base"));
|
||||
|
||||
Transaction transaction1 = await SendAsync(server, "http://example.com/base/testpath");
|
||||
Assert.True(transaction1.SetCookie.Contains("path=/base"));
|
||||
}
|
||||
|
||||
private static string FindClaimValue(Transaction transaction, string claimType)
|
||||
{
|
||||
XElement claim = transaction.ResponseElement.Elements("claim").SingleOrDefault(elt => elt.Attribute("type").Value == claimType);
|
||||
|
|
@ -364,9 +385,9 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
return me;
|
||||
}
|
||||
|
||||
private static TestServer CreateServer(Action<CookieAuthenticationOptions> configureOptions, Func<HttpContext, Task> testpath = null)
|
||||
private static TestServer CreateServer(Action<CookieAuthenticationOptions> configureOptions, Func<HttpContext, Task> testpath = null, Uri baseAddress = null)
|
||||
{
|
||||
return TestServer.Create(app =>
|
||||
var server = TestServer.Create(app =>
|
||||
{
|
||||
app.UseServices(services => services.AddDataProtection());
|
||||
app.UseCookieAuthentication(configureOptions);
|
||||
|
|
@ -406,6 +427,8 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
}
|
||||
});
|
||||
});
|
||||
server.BaseAddress = baseAddress;
|
||||
return server;
|
||||
}
|
||||
|
||||
private static void Describe(HttpResponse res, AuthenticationResult result)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.PipelineCore;
|
||||
using Microsoft.AspNet.Http.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Cookies.Infrastructure
|
||||
|
|
|
|||
|
|
@ -3,307 +3,681 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Security;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.DependencyInjection.Fallback;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Test
|
||||
{
|
||||
public class DefaultAuthorizationServiceTests
|
||||
{
|
||||
private IAuthorizationService BuildAuthorizationService(Action<IServiceCollection> setupServices = null)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddAuthorization();
|
||||
if (setupServices != null)
|
||||
{
|
||||
setupServices(services);
|
||||
}
|
||||
return services.BuildServiceProvider().GetRequiredService<IAuthorizationService>();
|
||||
}
|
||||
|
||||
private Mock<HttpContext> SetupContext(params ClaimsIdentity[] ids)
|
||||
{
|
||||
var context = new Mock<HttpContext>();
|
||||
context.SetupProperty(c => c.User);
|
||||
var user = new ClaimsPrincipal();
|
||||
user.AddIdentities(ids);
|
||||
context.Object.User = user;
|
||||
if (ids != null)
|
||||
{
|
||||
var results = new List<AuthenticationResult>();
|
||||
foreach (var id in ids)
|
||||
{
|
||||
results.Add(new AuthenticationResult(id, new AuthenticationProperties(), new AuthenticationDescription()));
|
||||
}
|
||||
context.Setup(c => c.AuthenticateAsync(It.IsAny<IEnumerable<string>>())).ReturnsAsync(results).Verifiable();
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Check_ShouldAllowIfClaimIsPresent()
|
||||
public async Task Authorize_ShouldAllowIfClaimIsPresent()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = new DefaultAuthorizationService(Enumerable.Empty<IAuthorizationPolicy>());
|
||||
var user = new ClaimsPrincipal(
|
||||
new ClaimsIdentity( new Claim[] { new Claim("Permission", "CanViewPage") }, "Basic")
|
||||
);
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage"));
|
||||
});
|
||||
});
|
||||
var context = SetupContext(new ClaimsIdentity(new Claim[] { new Claim("Permission", "CanViewPage") }, "Basic"));
|
||||
|
||||
// Act
|
||||
var allowed = authorizationService.Authorize(new Claim[] { new Claim("Permission", "CanViewPage") }, user);
|
||||
var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Check_ShouldAllowIfClaimIsAmongValues()
|
||||
public async Task Authorize_ShouldAllowIfClaimIsPresentWithSpecifiedAuthType()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = new DefaultAuthorizationService(Enumerable.Empty<IAuthorizationPolicy>());
|
||||
var user = new ClaimsPrincipal(
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage"));
|
||||
});
|
||||
});
|
||||
var context = SetupContext(new ClaimsIdentity(new Claim[] { new Claim("Permission", "CanViewPage") }, "Basic"));
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Authorize_ShouldAllowIfClaimIsAmongValues()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage", "CanViewAnything"));
|
||||
});
|
||||
});
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
new Claim("Permission", "CanViewPage"),
|
||||
new Claim[] {
|
||||
new Claim("Permission", "CanViewPage"),
|
||||
new Claim("Permission", "CanViewAnything")
|
||||
},
|
||||
},
|
||||
"Basic")
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = authorizationService.Authorize(new Claim[] { new Claim("Permission", "CanViewPage") }, user);
|
||||
var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Check_ShouldNotAllowIfClaimTypeIsNotPresent()
|
||||
public async Task Authorize_ShouldFailWhenAllRequirementsNotHandled()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = new DefaultAuthorizationService(Enumerable.Empty<IAuthorizationPolicy>());
|
||||
var user = new ClaimsPrincipal(
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage", "CanViewAnything"));
|
||||
});
|
||||
});
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
new Claim("SomethingElse", "CanViewPage"),
|
||||
new Claim[] {
|
||||
new Claim("SomethingElse", "CanViewPage"),
|
||||
},
|
||||
"Basic")
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = authorizationService.Authorize(new Claim[] { new Claim("Permission", "CanViewPage") }, user);
|
||||
var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Check_ShouldNotAllowIfClaimValueIsNotPresent()
|
||||
public async Task Authorize_ShouldNotAllowIfClaimTypeIsNotPresent()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = new DefaultAuthorizationService(Enumerable.Empty<IAuthorizationPolicy>());
|
||||
var user = new ClaimsPrincipal(
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage", "CanViewAnything"));
|
||||
});
|
||||
});
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
new Claim("Permission", "CanViewComment"),
|
||||
new Claim[] {
|
||||
new Claim("SomethingElse", "CanViewPage"),
|
||||
},
|
||||
"Basic")
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = authorizationService.Authorize(new Claim[] { new Claim("Permission", "CanViewPage") }, user);
|
||||
var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Check_ShouldNotAllowIfNoClaims()
|
||||
public async Task Authorize_ShouldNotAllowIfClaimValueIsNotPresent()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = new DefaultAuthorizationService(Enumerable.Empty<IAuthorizationPolicy>());
|
||||
var user = new ClaimsPrincipal(
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage"));
|
||||
});
|
||||
});
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
new Claim("Permission", "CanViewComment"),
|
||||
},
|
||||
"Basic")
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Authorize_ShouldNotAllowIfNoClaims()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage"));
|
||||
});
|
||||
});
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[0],
|
||||
"Basic")
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = authorizationService.Authorize(new Claim[] { new Claim("Permission", "CanViewPage") }, user);
|
||||
var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Check_ShouldNotAllowIfUserIsNull()
|
||||
public async Task Authorize_ShouldNotAllowIfUserIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = new DefaultAuthorizationService(Enumerable.Empty<IAuthorizationPolicy>());
|
||||
ClaimsPrincipal user = null;
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage"));
|
||||
});
|
||||
});
|
||||
var context = SetupContext();
|
||||
context.Object.User = null;
|
||||
|
||||
// Act
|
||||
var allowed = authorizationService.Authorize(new Claim[] { new Claim("Permission", "CanViewPage") }, user);
|
||||
var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Check_ShouldNotAllowIfUserIsNotAuthenticated()
|
||||
public async Task Authorize_ShouldNotAllowIfNotCorrectAuthType()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = new DefaultAuthorizationService(Enumerable.Empty<IAuthorizationPolicy>());
|
||||
var user = new ClaimsPrincipal(
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage"));
|
||||
});
|
||||
});
|
||||
var context = SetupContext(new ClaimsIdentity());
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Authorize_ShouldAllowWithNoAuthType()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Basic", policy => policy.RequiresClaim("Permission", "CanViewPage"));
|
||||
});
|
||||
});
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
new Claim("Permission", "CanViewComment"),
|
||||
new Claim[] {
|
||||
new Claim("Permission", "CanViewPage"),
|
||||
},
|
||||
"Basic")
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Authorize_ShouldNotAllowIfUnknownPolicy()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = BuildAuthorizationService();
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
new Claim("Permission", "CanViewComment"),
|
||||
},
|
||||
null)
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = authorizationService.Authorize(new Claim[] { new Claim("Permission", "CanViewPage") }, user);
|
||||
var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Check_ShouldApplyPoliciesInOrder()
|
||||
public async Task Authorize_CustomRolePolicy()
|
||||
{
|
||||
// Arrange
|
||||
string result = "";
|
||||
var policies = new IAuthorizationPolicy[] {
|
||||
new FakePolicy() {
|
||||
Order = 20,
|
||||
ApplyingAsyncAction = (context) => { result += "20"; }
|
||||
},
|
||||
new FakePolicy() {
|
||||
Order = -1,
|
||||
ApplyingAsyncAction = (context) => { result += "-1"; }
|
||||
},
|
||||
new FakePolicy() {
|
||||
Order = 30,
|
||||
ApplyingAsyncAction = (context) => { result += "30"; }
|
||||
},
|
||||
};
|
||||
|
||||
var authorizationService = new DefaultAuthorizationService(policies);
|
||||
|
||||
// Act
|
||||
var allowed = authorizationService.Authorize(Enumerable.Empty<Claim>(), null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("-12030", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Check_ShouldInvokeApplyingApplyAppliedInOrder()
|
||||
{
|
||||
// Arrange
|
||||
string result = "";
|
||||
var policies = new IAuthorizationPolicy[] {
|
||||
new FakePolicy() {
|
||||
Order = 20,
|
||||
ApplyingAsyncAction = (context) => { result += "Applying20"; },
|
||||
ApplyAsyncAction = (context) => { result += "Apply20"; },
|
||||
AppliedAsyncAction = (context) => { result += "Applied20"; }
|
||||
},
|
||||
new FakePolicy() {
|
||||
Order = -1,
|
||||
ApplyingAsyncAction = (context) => { result += "Applying-1"; },
|
||||
ApplyAsyncAction = (context) => { result += "Apply-1"; },
|
||||
AppliedAsyncAction = (context) => { result += "Applied-1"; }
|
||||
},
|
||||
new FakePolicy() {
|
||||
Order = 30,
|
||||
ApplyingAsyncAction = (context) => { result += "Applying30"; },
|
||||
ApplyAsyncAction = (context) => { result += "Apply30"; },
|
||||
AppliedAsyncAction = (context) => { result += "Applied30"; }
|
||||
},
|
||||
};
|
||||
|
||||
var authorizationService = new DefaultAuthorizationService(policies);
|
||||
|
||||
// Act
|
||||
var allowed = authorizationService.Authorize(Enumerable.Empty<Claim>(), null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Applying-1Applying20Applying30Apply-1Apply20Apply30Applied-1Applied20Applied30", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Check_ShouldConvertNullClaimsToEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
IList<Claim> claims = null;
|
||||
var policies = new IAuthorizationPolicy[] {
|
||||
new FakePolicy() {
|
||||
Order = 20,
|
||||
ApplyingAsyncAction = (context) => { claims = context.Claims; }
|
||||
}
|
||||
};
|
||||
|
||||
var authorizationService = new DefaultAuthorizationService(policies);
|
||||
|
||||
// Act
|
||||
var allowed = authorizationService.Authorize(Enumerable.Empty<Claim>(), null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(claims);
|
||||
Assert.Equal(0, claims.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Check_ShouldThrowWhenPoliciesDontStop()
|
||||
{
|
||||
// Arrange
|
||||
var policies = new IAuthorizationPolicy[] {
|
||||
new FakePolicy() {
|
||||
ApplyAsyncAction = (context) => { context.Retry = true; }
|
||||
}
|
||||
};
|
||||
|
||||
var authorizationService = new DefaultAuthorizationService(policies);
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
Exception ex = Assert.Throws<InvalidOperationException>(() => authorizationService.Authorize(Enumerable.Empty<Claim>(), null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Check_ApplyCanMutateCheckedClaims()
|
||||
{
|
||||
|
||||
// Arrange
|
||||
var user = new ClaimsPrincipal(
|
||||
new ClaimsIdentity( new Claim[] { new Claim("Permission", "CanDeleteComments") }, "Basic")
|
||||
var policy = new AuthorizationPolicyBuilder().RequiresRole("Administrator")
|
||||
.RequiresClaim(ClaimTypes.Role, "User");
|
||||
var authorizationService = BuildAuthorizationService();
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
new Claim(ClaimTypes.Role, "User"),
|
||||
new Claim(ClaimTypes.Role, "Administrator")
|
||||
},
|
||||
"Basic")
|
||||
);
|
||||
|
||||
var policies = new IAuthorizationPolicy[] {
|
||||
new FakePolicy() {
|
||||
ApplyAsyncAction = (context) => {
|
||||
// for instance, if user owns the comment
|
||||
if(!context.Claims.Any(claim => claim.Type == "Permission" && claim.Value == "CanDeleteComments"))
|
||||
{
|
||||
context.Claims.Add(new Claim("Permission", "CanDeleteComments"));
|
||||
context.Retry = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var authorizationService = new DefaultAuthorizationService(policies);
|
||||
|
||||
// Act
|
||||
var allowed = authorizationService.Authorize(Enumerable.Empty<Claim>(), user);
|
||||
var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Check_PoliciesCanMutateUsersClaims()
|
||||
public async Task Authorize_HasAnyClaimOfTypePolicy()
|
||||
{
|
||||
|
||||
// Arrange
|
||||
var user = new ClaimsPrincipal(
|
||||
new ClaimsIdentity(new Claim[0], "Basic")
|
||||
var policy = new AuthorizationPolicyBuilder().RequiresClaim(ClaimTypes.Role);
|
||||
var authorizationService = BuildAuthorizationService();
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
new Claim(ClaimTypes.Role, ""),
|
||||
},
|
||||
"Basic")
|
||||
);
|
||||
|
||||
var policies = new IAuthorizationPolicy[] {
|
||||
new FakePolicy() {
|
||||
ApplyAsyncAction = (context) => {
|
||||
if (!context.Authorized)
|
||||
{
|
||||
context.UserClaims.Add(new Claim("Permission", "CanDeleteComments"));
|
||||
context.Retry = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var authorizationService = new DefaultAuthorizationService(policies);
|
||||
|
||||
// Act
|
||||
var allowed = authorizationService.Authorize(new Claim("Permission", "CanDeleteComments"), user);
|
||||
var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Authorize_PolicyCanAuthenticationTypeWithNameClaim()
|
||||
{
|
||||
// Arrange
|
||||
var policy = new AuthorizationPolicyBuilder("AuthType").RequiresClaim(ClaimTypes.Name);
|
||||
var authorizationService = BuildAuthorizationService();
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, "Name") }, "AuthType")
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RolePolicyCanRequireSingleRole()
|
||||
{
|
||||
// Arrange
|
||||
var policy = new AuthorizationPolicyBuilder("AuthType").RequiresRole("Admin");
|
||||
var authorizationService = BuildAuthorizationService();
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Role, "Admin") }, "AuthType")
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RolePolicyCanRequireOneOfManyRoles()
|
||||
{
|
||||
// Arrange
|
||||
var policy = new AuthorizationPolicyBuilder("AuthType").RequiresRole("Admin", "Users");
|
||||
var authorizationService = BuildAuthorizationService();
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Role, "Users") }, "AuthType"));
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RolePolicyCanBlockWrongRole()
|
||||
{
|
||||
// Arrange
|
||||
var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage");
|
||||
var authorizationService = BuildAuthorizationService();
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
new Claim(ClaimTypes.Role, "Nope"),
|
||||
},
|
||||
"AuthType")
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync(policy.Build(), context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RolePolicyCanBlockNoRole()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Basic", policy => policy.RequiresRole("Admin", "Users"));
|
||||
});
|
||||
});
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
},
|
||||
"AuthType")
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PolicyFailsWithNoRequirements()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Basic", policy => { });
|
||||
});
|
||||
});
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
new Claim(ClaimTypes.Name, "Name"),
|
||||
},
|
||||
"AuthType")
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync("Basic", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanApproveAnyAuthenticatedUser()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Any", policy => policy.RequireAuthenticatedUser());
|
||||
});
|
||||
});
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
new Claim(ClaimTypes.Name, "Name"),
|
||||
},
|
||||
"AuthType")
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync("Any", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanBlockNonAuthenticatedUser()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Any", policy => policy.RequireAuthenticatedUser());
|
||||
});
|
||||
});
|
||||
var context = SetupContext(new ClaimsIdentity());
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync("Any", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
public class CustomRequirement : IAuthorizationRequirement { }
|
||||
public class CustomHandler : AuthorizationHandler<CustomRequirement>
|
||||
{
|
||||
public override Task<bool> CheckAsync(AuthorizationContext context, CustomRequirement requirement)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CustomReqWithNoHandlerFails()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Custom", policy => policy.Requirements.Add(new CustomRequirement()));
|
||||
});
|
||||
});
|
||||
var context = SetupContext();
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync("Custom", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CustomReqWithHandlerSucceeds()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.AddTransient<IAuthorizationHandler, CustomHandler>();
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Custom", policy => policy.Requirements.Add(new CustomRequirement()));
|
||||
});
|
||||
});
|
||||
var context = SetupContext();
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync("Custom", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
public class PassThroughRequirement : AuthorizationHandler<PassThroughRequirement>, IAuthorizationRequirement
|
||||
{
|
||||
public PassThroughRequirement(bool succeed)
|
||||
{
|
||||
Succeed = succeed;
|
||||
}
|
||||
|
||||
public bool Succeed { get; set; }
|
||||
|
||||
public override Task<bool> CheckAsync(AuthorizationContext context, PassThroughRequirement requirement)
|
||||
{
|
||||
return Task.FromResult(Succeed);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task PassThroughRequirementWillSucceedWithoutCustomHandler(bool shouldSucceed)
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("Passthrough", policy => policy.Requirements.Add(new PassThroughRequirement(shouldSucceed)));
|
||||
});
|
||||
});
|
||||
var context = SetupContext();
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync("Passthrough", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(shouldSucceed, allowed);
|
||||
}
|
||||
|
||||
public async Task CanCombinePolicies()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
var basePolicy = new AuthorizationPolicyBuilder().RequiresClaim("Base", "Value").Build();
|
||||
options.AddPolicy("Combineed", policy => policy.Combine(basePolicy).RequiresClaim("Claim", "Exists"));
|
||||
});
|
||||
});
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
new Claim("Base", "Value"),
|
||||
new Claim("Claim", "Exists")
|
||||
},
|
||||
"AuthType")
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync("Combined", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
public async Task CombinePoliciesWillFailIfBasePolicyFails()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
var basePolicy = new AuthorizationPolicyBuilder().RequiresClaim("Base", "Value").Build();
|
||||
options.AddPolicy("Combined", policy => policy.Combine(basePolicy).RequiresClaim("Claim", "Exists"));
|
||||
});
|
||||
});
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
new Claim("Claim", "Exists")
|
||||
},
|
||||
"AuthType")
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync("Combined", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
public async Task CombinedPoliciesWillFailIfExtraRequirementFails()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationService = BuildAuthorizationService(services =>
|
||||
{
|
||||
services.ConfigureAuthorization(options =>
|
||||
{
|
||||
var basePolicy = new AuthorizationPolicyBuilder().RequiresClaim("Base", "Value").Build();
|
||||
options.AddPolicy("Combined", policy => policy.Combine(basePolicy).RequiresClaim("Claim", "Exists"));
|
||||
});
|
||||
});
|
||||
var context = SetupContext(
|
||||
new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
new Claim("Base", "Value"),
|
||||
},
|
||||
"AuthType")
|
||||
);
|
||||
|
||||
// Act
|
||||
var allowed = await authorizationService.AuthorizeAsync("Combined", context.Object);
|
||||
|
||||
// Assert
|
||||
Assert.False(allowed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Security;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Test
|
||||
{
|
||||
public class FakePolicy : IAuthorizationPolicy
|
||||
{
|
||||
|
||||
public int Order { get; set; }
|
||||
|
||||
public Task ApplyingAsync(AuthorizationPolicyContext context)
|
||||
{
|
||||
if (ApplyingAsyncAction != null)
|
||||
{
|
||||
ApplyingAsyncAction(context);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task ApplyAsync(AuthorizationPolicyContext context)
|
||||
{
|
||||
if (ApplyAsyncAction != null)
|
||||
{
|
||||
ApplyAsyncAction(context);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
|
||||
}
|
||||
|
||||
public Task AppliedAsync(AuthorizationPolicyContext context)
|
||||
{
|
||||
if (AppliedAsyncAction != null)
|
||||
{
|
||||
AppliedAsyncAction(context);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Action<AuthorizationPolicyContext> ApplyingAsyncAction { get; set;}
|
||||
|
||||
public Action<AuthorizationPolicyContext> ApplyAsyncAction { get; set;}
|
||||
|
||||
public Action<AuthorizationPolicyContext> AppliedAsyncAction { get; set;}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Security.Notifications;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Security.OAuthBearer
|
||||
{
|
||||
public class OAuthBearerMiddlewareTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task BearerTokenValidation()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/tushartest.onmicrosoft.com";
|
||||
options.Audience = "https://TusharTest.onmicrosoft.com/TodoListService-ManualJwt";
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateLifetime = false
|
||||
};
|
||||
});
|
||||
string newBearerToken = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImtyaU1QZG1Cdng2OHNrVDgtbVBBQjNCc2VlQSJ9.eyJhdWQiOiJodHRwczovL1R1c2hhclRlc3Qub25taWNyb3NvZnQuY29tL1RvZG9MaXN0U2VydmljZS1NYW51YWxKd3QiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9hZmJlY2UwMy1hZWFhLTRmM2YtODVlNy1jZTA4ZGQyMGNlNTAvIiwiaWF0IjoxNDE4MzMwNjE0LCJuYmYiOjE0MTgzMzA2MTQsImV4cCI6MTQxODMzNDUxNCwidmVyIjoiMS4wIiwidGlkIjoiYWZiZWNlMDMtYWVhYS00ZjNmLTg1ZTctY2UwOGRkMjBjZTUwIiwiYW1yIjpbInB3ZCJdLCJvaWQiOiI1Mzk3OTdjMi00MDE5LTQ2NTktOWRiNS03MmM0Yzc3NzhhMzMiLCJ1cG4iOiJWaWN0b3JAVHVzaGFyVGVzdC5vbm1pY3Jvc29mdC5jb20iLCJ1bmlxdWVfbmFtZSI6IlZpY3RvckBUdXNoYXJUZXN0Lm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IkQyMm9aMW9VTzEzTUFiQXZrdnFyd2REVE80WXZJdjlzMV9GNWlVOVUwYnciLCJmYW1pbHlfbmFtZSI6Ikd1cHRhIiwiZ2l2ZW5fbmFtZSI6IlZpY3RvciIsImFwcGlkIjoiNjEzYjVhZjgtZjJjMy00MWI2LWExZGMtNDE2Yzk3ODAzMGI3IiwiYXBwaWRhY3IiOiIwIiwic2NwIjoidXNlcl9pbXBlcnNvbmF0aW9uIiwiYWNyIjoiMSJ9.N_Kw1EhoVGrHbE6hOcm7ERdZ7paBQiNdObvp2c6T6n5CE8p0fZqmUd-ya_EqwElcD6SiKSiP7gj0gpNUnOJcBl_H2X8GseaeeMxBrZdsnDL8qecc6_ygHruwlPltnLTdka67s1Ow4fDSHaqhVTEk6lzGmNEcbNAyb0CxQxU6o7Fh0yHRiWoLsT8yqYk8nKzsHXfZBNby4aRo3_hXaa4i0SZLYfDGGYPdttG4vT_u54QGGd4Wzbonv2gjDlllOVGOwoJS6kfl1h8mk0qxdiIaT_ChbDWgkWvTB7bTvBE-EgHgV0XmAo0WtJeSxgjsG3KhhEPsONmqrSjhIUV4IVnF2w";
|
||||
var response = await SendAsync(server, "http://example.com/oauth", newBearerToken);
|
||||
response.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CustomHeaderReceived()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Notifications.MessageReceived = HeaderReceived;
|
||||
});
|
||||
|
||||
var response = await SendAsync(server, "http://example.com/oauth", "someHeader someblob");
|
||||
response.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
private static Task HeaderReceived(MessageReceivedNotification<HttpContext, OAuthBearerAuthenticationOptions> notification)
|
||||
{
|
||||
List<Claim> claims =
|
||||
new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.Email, "bob@contoso.com"),
|
||||
new Claim(ClaimsIdentity.DefaultNameClaimType, "bob"),
|
||||
};
|
||||
|
||||
notification.AuthenticationTicket = new AuthenticationTicket(new ClaimsIdentity(claims, notification.Options.AuthenticationType), new Http.Security.AuthenticationProperties());
|
||||
notification.HandleResponse();
|
||||
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NoHeaderReceived()
|
||||
{
|
||||
var server = CreateServer(options => { });
|
||||
var response = await SendAsync(server, "http://example.com/oauth");
|
||||
response.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderWithoutBearerReceived()
|
||||
{
|
||||
var server = CreateServer(options => { });
|
||||
var response = await SendAsync(server, "http://example.com/oauth","Token");
|
||||
response.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CustomTokenReceived()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Notifications.SecurityTokenReceived = SecurityTokenReceived;
|
||||
});
|
||||
|
||||
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
|
||||
response.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
private static Task SecurityTokenReceived(SecurityTokenReceivedNotification<HttpContext, OAuthBearerAuthenticationOptions> notification)
|
||||
{
|
||||
List<Claim> claims =
|
||||
new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.Email, "bob@contoso.com"),
|
||||
new Claim(ClaimsIdentity.DefaultNameClaimType, "bob"),
|
||||
};
|
||||
|
||||
notification.AuthenticationTicket = new AuthenticationTicket(new ClaimsIdentity(claims, notification.Options.AuthenticationType), new Http.Security.AuthenticationProperties());
|
||||
notification.HandleResponse();
|
||||
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CustomTokenValidated()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Notifications.SecurityTokenValidated = SecurityTokenValidated;
|
||||
options.SecurityTokenValidators = new List<ISecurityTokenValidator>{new BlobTokenValidator(options.AuthenticationType)};
|
||||
});
|
||||
|
||||
var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob");
|
||||
response.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
private static Task SecurityTokenValidated(SecurityTokenValidatedNotification<HttpContext, OAuthBearerAuthenticationOptions> notification)
|
||||
{
|
||||
List<Claim> claims =
|
||||
new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.Email, "bob@contoso.com"),
|
||||
new Claim(ClaimsIdentity.DefaultNameClaimType, "bob"),
|
||||
};
|
||||
|
||||
notification.AuthenticationTicket = new AuthenticationTicket(new ClaimsIdentity(claims, notification.Options.AuthenticationType), new Http.Security.AuthenticationProperties());
|
||||
notification.HandleResponse();
|
||||
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RetrievingTokenFromAlternateLocation()
|
||||
{
|
||||
var server = CreateServer(options => {
|
||||
options.Notifications.MessageReceived = MessageReceived;
|
||||
options.Notifications.SecurityTokenReceived = SecurityTokenReceived;
|
||||
});
|
||||
var response = await SendAsync(server, "http://example.com/oauth", "Bearer Token");
|
||||
response.Response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
private static Task MessageReceived(MessageReceivedNotification<HttpContext, OAuthBearerAuthenticationOptions> notification)
|
||||
{
|
||||
notification.Token = "CustomToken";
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
class BlobTokenValidator : ISecurityTokenValidator
|
||||
{
|
||||
|
||||
public BlobTokenValidator(string authenticationType)
|
||||
{
|
||||
AuthenticationType = authenticationType;
|
||||
}
|
||||
|
||||
public string AuthenticationType { get; set; }
|
||||
|
||||
public bool CanValidateToken
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public int MaximumTokenSizeInBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
return 2*2*1024;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanReadToken(string securityToken)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
|
||||
{
|
||||
validatedToken = null;
|
||||
List<Claim> claims =
|
||||
new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.Email, "bob@contoso.com"),
|
||||
new Claim(ClaimsIdentity.DefaultNameClaimType, "bob"),
|
||||
};
|
||||
|
||||
return new ClaimsPrincipal(new ClaimsIdentity(claims, AuthenticationType));
|
||||
}
|
||||
}
|
||||
|
||||
private static TestServer CreateServer(Action<OAuthBearerAuthenticationOptions> configureOptions, Func<HttpContext, bool> handler = null)
|
||||
{
|
||||
return TestServer.Create(app =>
|
||||
{
|
||||
app.UseServices(services =>
|
||||
{
|
||||
services.AddDataProtection();
|
||||
});
|
||||
|
||||
if (configureOptions != null)
|
||||
{
|
||||
app.UseOAuthBearerAuthentication(configureOptions);
|
||||
}
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var req = context.Request;
|
||||
var res = context.Response;
|
||||
if (req.Path == new PathString("/oauth"))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
await next();
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<Transaction> SendAsync(TestServer server, string uri, string authorizationHeader = null)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
if (!string.IsNullOrEmpty(authorizationHeader))
|
||||
{
|
||||
request.Headers.Add("Authorization", authorizationHeader);
|
||||
}
|
||||
|
||||
var transaction = new Transaction
|
||||
{
|
||||
Request = request,
|
||||
Response = await server.CreateClient().SendAsync(request),
|
||||
};
|
||||
|
||||
transaction.ResponseText = await transaction.Response.Content.ReadAsStringAsync();
|
||||
|
||||
if (transaction.Response.Content != null &&
|
||||
transaction.Response.Content.Headers.ContentType != null &&
|
||||
transaction.Response.Content.Headers.ContentType.MediaType == "text/xml")
|
||||
{
|
||||
transaction.ResponseElement = XElement.Parse(transaction.ResponseText);
|
||||
}
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private class Transaction
|
||||
{
|
||||
public HttpRequestMessage Request { get; set; }
|
||||
public HttpResponseMessage Response { get; set; }
|
||||
public IList<string> SetCookie { get; set; }
|
||||
public string ResponseText { get; set; }
|
||||
public XElement ResponseElement { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,362 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
using Microsoft.AspNet.Security.Cookies;
|
||||
using Microsoft.AspNet.Security.DataHandler;
|
||||
using Microsoft.AspNet.Security.DataProtection;
|
||||
using Microsoft.AspNet.Security.OpenIdConnect;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Tests.OpenIdConnect
|
||||
{
|
||||
public class OpenIdConnectMiddlewareTests
|
||||
{
|
||||
static string noncePrefix = "OpenIdConnect." + "Nonce.";
|
||||
static string nonceDelimiter = ".";
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillTriggerRedirect()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/common";
|
||||
options.ClientId = "Test Id";
|
||||
options.SignInAsAuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType;
|
||||
});
|
||||
var transaction = await SendAsync(server, "https://example.com/challenge");
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
var location = transaction.Response.Headers.Location.ToString();
|
||||
location.ShouldContain("https://login.windows.net/common/oauth2/authorize?");
|
||||
location.ShouldContain("client_id=");
|
||||
location.ShouldContain("&response_type=");
|
||||
location.ShouldContain("&scope=");
|
||||
location.ShouldContain("&state=");
|
||||
location.ShouldContain("&response_mode=");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillSetNonceCookie()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/common";
|
||||
options.ClientId = "Test Id";
|
||||
});
|
||||
var transaction = await SendAsync(server, "https://example.com/challenge");
|
||||
transaction.SetCookie.Single().ShouldContain("OpenIdConnect.nonce.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillSetDefaultScope()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/common";
|
||||
options.ClientId = "Test Id";
|
||||
});
|
||||
var transaction = await SendAsync(server, "https://example.com/challenge");
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
transaction.Response.Headers.Location.Query.ShouldContain("&scope=" + Uri.EscapeDataString("openid profile"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillUseOptionsProperties()
|
||||
{
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/common";
|
||||
options.ClientId = "Test Id";
|
||||
options.SignInAsAuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType;
|
||||
options.Scope = "https://www.googleapis.com/auth/plus.login";
|
||||
options.ResponseType = "id_token";
|
||||
});
|
||||
var transaction = await SendAsync(server, "https://example.com/challenge");
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
var query = transaction.Response.Headers.Location.Query;
|
||||
query.ShouldContain("scope=" + Uri.EscapeDataString("https://www.googleapis.com/auth/plus.login"));
|
||||
query.ShouldContain("response_type=" + Uri.EscapeDataString("id_token"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChallengeWillUseNotifications()
|
||||
{
|
||||
ISecureDataFormat<AuthenticationProperties> stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest"));
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/common";
|
||||
options.ClientId = "Test Id";
|
||||
options.Notifications = new OpenIdConnectAuthenticationNotifications
|
||||
{
|
||||
MessageReceived = notification =>
|
||||
{
|
||||
notification.ProtocolMessage.Scope = "test openid profile";
|
||||
notification.HandleResponse();
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var properties = new AuthenticationProperties();
|
||||
var state = stateFormat.Protect(properties);
|
||||
var transaction = await SendAsync(server,"https://example.com/challenge");
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task SignOutWithDefaultRedirectUri()
|
||||
{
|
||||
ISecureDataFormat<AuthenticationProperties> stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest"));
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/common";
|
||||
options.ClientId = "Test Id";
|
||||
});
|
||||
|
||||
var transaction = await SendAsync(server, "https://example.com/signout");
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
transaction.Response.Headers.Location.AbsoluteUri.ShouldBe("https://login.windows.net/common/oauth2/logout");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SignOutWithCustomRedirectUri()
|
||||
{
|
||||
ISecureDataFormat<AuthenticationProperties> stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest"));
|
||||
var server = CreateServer(options =>
|
||||
{
|
||||
options.Authority = "https://login.windows.net/common";
|
||||
options.ClientId = "Test Id";
|
||||
options.PostLogoutRedirectUri = "https://example.com/logout";
|
||||
});
|
||||
|
||||
var transaction = await SendAsync(server, "https://example.com/signout");
|
||||
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
|
||||
transaction.Response.Headers.Location.AbsoluteUri.ShouldContain(Uri.EscapeDataString("https://example.com/logout"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
// Test Cases for calculating the expiration time of cookie from cookie name
|
||||
public void NonceCookieExpirationTime()
|
||||
{
|
||||
DateTime utcNow = DateTime.UtcNow;
|
||||
|
||||
GetNonceExpirationTime(noncePrefix + DateTime.MaxValue.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(DateTime.MaxValue);
|
||||
|
||||
GetNonceExpirationTime(noncePrefix + DateTime.MinValue.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue + TimeSpan.FromHours(1));
|
||||
|
||||
GetNonceExpirationTime(noncePrefix + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(utcNow + TimeSpan.FromHours(1));
|
||||
|
||||
GetNonceExpirationTime(noncePrefix, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
|
||||
|
||||
GetNonceExpirationTime("", TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
|
||||
|
||||
GetNonceExpirationTime(noncePrefix + noncePrefix, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
|
||||
|
||||
GetNonceExpirationTime(noncePrefix + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(utcNow + TimeSpan.FromHours(1));
|
||||
|
||||
GetNonceExpirationTime(utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
|
||||
}
|
||||
|
||||
private static TestServer CreateServer(Action<OpenIdConnectAuthenticationOptions> configureOptions, Func<HttpContext, Task> handler = null)
|
||||
{
|
||||
return TestServer.Create(app =>
|
||||
{
|
||||
app.UseServices(services =>
|
||||
{
|
||||
services.AddDataProtection();
|
||||
services.Configure<ExternalAuthenticationOptions>(options =>
|
||||
{
|
||||
options.SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType;
|
||||
});
|
||||
});
|
||||
|
||||
app.UseCookieAuthentication(options =>
|
||||
{
|
||||
options.AuthenticationType = "OpenIdConnect";
|
||||
});
|
||||
app.UseOpenIdConnectAuthentication(configureOptions);
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var req = context.Request;
|
||||
var res = context.Response;
|
||||
if (req.Path == new PathString("/challenge"))
|
||||
{
|
||||
res.Challenge("OpenIdConnect");
|
||||
res.StatusCode = 401;
|
||||
}
|
||||
else if (req.Path == new PathString("/signin"))
|
||||
{
|
||||
res.SignIn();
|
||||
}
|
||||
else if (req.Path == new PathString("/signout"))
|
||||
{
|
||||
res.SignOut(OpenIdConnectAuthenticationDefaults.AuthenticationType);
|
||||
}
|
||||
else if (handler != null)
|
||||
{
|
||||
await handler(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
await next();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<Transaction> SendAsync(TestServer server, string uri, string cookieHeader = null)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
if (!string.IsNullOrEmpty(cookieHeader))
|
||||
{
|
||||
request.Headers.Add("Cookie", cookieHeader);
|
||||
}
|
||||
var transaction = new Transaction
|
||||
{
|
||||
Request = request,
|
||||
Response = await server.CreateClient().SendAsync(request),
|
||||
};
|
||||
if (transaction.Response.Headers.Contains("Set-Cookie"))
|
||||
{
|
||||
transaction.SetCookie = transaction.Response.Headers.GetValues("Set-Cookie").ToList();
|
||||
}
|
||||
transaction.ResponseText = await transaction.Response.Content.ReadAsStringAsync();
|
||||
|
||||
if (transaction.Response.Content != null &&
|
||||
transaction.Response.Content.Headers.ContentType != null &&
|
||||
transaction.Response.Content.Headers.ContentType.MediaType == "text/xml")
|
||||
{
|
||||
transaction.ResponseElement = XElement.Parse(transaction.ResponseText);
|
||||
}
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private class Transaction
|
||||
{
|
||||
public HttpRequestMessage Request { get; set; }
|
||||
public HttpResponseMessage Response { get; set; }
|
||||
|
||||
public IList<string> SetCookie { get; set; }
|
||||
|
||||
public string ResponseText { get; set; }
|
||||
public XElement ResponseElement { get; set; }
|
||||
|
||||
public string AuthenticationCookieValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SetCookie != null && SetCookie.Count > 0)
|
||||
{
|
||||
var authCookie = SetCookie.SingleOrDefault(c => c.Contains(".AspNet.Cookie="));
|
||||
if (authCookie != null)
|
||||
{
|
||||
return authCookie.Substring(0, authCookie.IndexOf(';'));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string FindClaimValue(string claimType)
|
||||
{
|
||||
XElement claim = ResponseElement.Elements("claim").SingleOrDefault(elt => elt.Attribute("type").Value == claimType);
|
||||
if (claim == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return claim.Attribute("value").Value;
|
||||
}
|
||||
}
|
||||
private static void Describe(HttpResponse res, ClaimsIdentity identity)
|
||||
{
|
||||
res.StatusCode = 200;
|
||||
res.ContentType = "text/xml";
|
||||
var xml = new XElement("xml");
|
||||
if (identity != null)
|
||||
{
|
||||
xml.Add(identity.Claims.Select(claim => new XElement("claim", new XAttribute("type", claim.Type), new XAttribute("value", claim.Value))));
|
||||
}
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
using (var writer = new XmlTextWriter(memory, Encoding.UTF8))
|
||||
{
|
||||
xml.WriteTo(writer);
|
||||
}
|
||||
res.Body.Write(memory.ToArray(), 0, memory.ToArray().Length);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestHttpMessageHandler : HttpMessageHandler
|
||||
{
|
||||
public Func<HttpRequestMessage, HttpResponseMessage> Sender { get; set; }
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
|
||||
{
|
||||
if (Sender != null)
|
||||
{
|
||||
return Task.FromResult(Sender(request));
|
||||
}
|
||||
|
||||
return Task.FromResult<HttpResponseMessage>(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpResponseMessage ReturnJsonResponse(object content)
|
||||
{
|
||||
var res = new HttpResponseMessage(HttpStatusCode.OK);
|
||||
var text = JsonConvert.SerializeObject(content);
|
||||
res.Content = new StringContent(text, Encoding.UTF8, "application/json");
|
||||
return res;
|
||||
}
|
||||
|
||||
private static DateTime GetNonceExpirationTime(string keyname, TimeSpan nonceLifetime)
|
||||
{
|
||||
DateTime nonceTime = DateTime.MinValue;
|
||||
string timestamp = null;
|
||||
int endOfTimestamp;
|
||||
if (keyname.StartsWith(noncePrefix, StringComparison.Ordinal))
|
||||
{
|
||||
timestamp = keyname.Substring(noncePrefix.Length);
|
||||
endOfTimestamp = timestamp.IndexOf('.');
|
||||
|
||||
if (endOfTimestamp != -1)
|
||||
{
|
||||
timestamp = timestamp.Substring(0, endOfTimestamp);
|
||||
try
|
||||
{
|
||||
nonceTime = DateTime.FromBinary(Convert.ToInt64(timestamp, CultureInfo.InvariantCulture));
|
||||
if ((nonceTime >= DateTime.UtcNow) && ((DateTime.MaxValue - nonceTime) < nonceLifetime))
|
||||
nonceTime = DateTime.MaxValue;
|
||||
else
|
||||
nonceTime += nonceLifetime;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
return nonceTime;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ using System.Linq;
|
|||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.PipelineCore;
|
||||
using Microsoft.AspNet.Http.Core;
|
||||
using Microsoft.AspNet.Security.Infrastructure;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
using Microsoft.Framework.Runtime;
|
||||
|
||||
namespace Microsoft.AspNet.Security
|
||||
{
|
||||
public class TestApplicationEnvironment : IApplicationEnvironment
|
||||
{
|
||||
public string ApplicationBasePath
|
||||
{
|
||||
get { return Environment.CurrentDirectory; }
|
||||
}
|
||||
|
||||
public string ApplicationName
|
||||
{
|
||||
get { return "Test App environment"; }
|
||||
}
|
||||
|
||||
public string Configuration
|
||||
{
|
||||
get { return "Test"; }
|
||||
}
|
||||
|
||||
public FrameworkName RuntimeFramework
|
||||
{
|
||||
get { return new FrameworkName(".NETFramework", new Version(4, 5)); }
|
||||
}
|
||||
|
||||
public string Version
|
||||
{
|
||||
get { return "1.0.0"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,13 +7,15 @@
|
|||
"Microsoft.AspNet.Security.Facebook": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.Google": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.MicrosoftAccount": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.OAuthBearer": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.OpenIdConnect": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.Twitter": "1.0.0-*",
|
||||
"Microsoft.AspNet.TestHost": "1.0.0-*",
|
||||
"Moq": "4.2.1312.1622",
|
||||
"Xunit.KRunner": "1.0.0-*"
|
||||
"xunit.runner.kre": "1.0.0-*"
|
||||
},
|
||||
"commands": {
|
||||
"test": "Xunit.KRunner"
|
||||
"test": "xunit.runner.kre"
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": {
|
||||
|
|
|
|||
Loading…
Reference in New Issue