Merge branch 'release' of github.com:aspnet/Security into release

This commit is contained in:
Wei Wang 2015-01-28 18:30:29 -08:00
commit 9743b590ab
87 changed files with 4302 additions and 1064 deletions

View File

@ -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>

View File

@ -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.

View File

@ -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

View File

@ -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 %*

View File

@ -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 "$@"

View File

@ -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>

View File

@ -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");
});
}
}
}

View File

@ -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"
}

View File

@ -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)
{

View File

@ -24,7 +24,6 @@ namespace Microsoft.AspNet.Security.Cookies
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType;
ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
CookiePath = "/";
ExpireTimeSpan = TimeSpan.FromDays(14);
SlidingExpiration = true;
CookieHttpOnly = true;

View File

@ -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;

View File

@ -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)
{

View File

@ -6,6 +6,11 @@
},
"frameworks": {
"aspnet50": {},
"aspnetcore50": {}
"aspnetcore50": {
"dependencies": {
"System.Dynamic.Runtime": "4.0.0-beta-*",
"System.ObjectModel": "4.0.10-beta-*"
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -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;

View File

@ -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"));

View File

@ -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
}
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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-*"
}
}
}

View File

@ -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>

View File

@ -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
{
}
}

View File

@ -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)
{
}
}
}

View File

@ -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; }
}
}

View File

@ -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.

View File

@ -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;

View File

@ -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
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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 &apos;{0}&apos; 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);
}
}
}
}

View File

@ -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>

View File

@ -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-*"
}
}
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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; }
}
}

View File

@ -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";
}
}

View File

@ -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
});
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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 &quot;OpenIdConnectMessage.Error was not null, indicating an error. Error: &apos;{0}&apos;. Error_Description (may be empty): &apos;{1}&apos;. Error_Uri (may be empty): &apos;{2}&apos;.&quot;.
/// </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: &apos;{0}&apos;..
/// </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);
}
}
}
}

View File

@ -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-*"
}
}
}
}

View File

@ -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
{

View File

@ -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
{

View File

@ -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>

View File

@ -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);
}
}
}

View File

@ -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);
//}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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)
)
);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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 { }
}

View File

@ -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);
}
}

View File

@ -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
{
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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();
}

View File

@ -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;

View File

@ -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
{

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}
}

View File

@ -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; }
}
}
}

View File

@ -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; }
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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-*"
},

View File

@ -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)

View File

@ -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

View File

@ -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);
}
}
}
}

View File

@ -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;}
}
}

View File

@ -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; }
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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"; }
}
}
}

View File

@ -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": {