Port Facebook middleware from Katana.

This commit is contained in:
Chris Ross 2014-08-07 17:19:59 -07:00
parent fabdb3b9df
commit 919fa0c195
26 changed files with 1373 additions and 17 deletions

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.21916.0
VisualStudioVersion = 14.0.22013.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9F-44F5-8131-EA5CAC053652}"
EndProject
@ -22,6 +22,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
global.json = global.json
EndProjectSection
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.Facebook", "src\Microsoft.AspNet.Security.Facebook\Microsoft.AspNet.Security.Facebook.kproj", "{3984651C-FD44-4394-8793-3D14EE348C04}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SocialSample", "samples\SocialSample\SocialSample.kproj", "{8C73D216-332D-41D8-BFD0-45BC4BC36552}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -73,6 +76,26 @@ Global
{8DA26CD1-1302-4CFD-9270-9FA1B7C6138B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{8DA26CD1-1302-4CFD-9270-9FA1B7C6138B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{8DA26CD1-1302-4CFD-9270-9FA1B7C6138B}.Release|x86.ActiveCfg = Release|Any CPU
{3984651C-FD44-4394-8793-3D14EE348C04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3984651C-FD44-4394-8793-3D14EE348C04}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3984651C-FD44-4394-8793-3D14EE348C04}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{3984651C-FD44-4394-8793-3D14EE348C04}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{3984651C-FD44-4394-8793-3D14EE348C04}.Debug|x86.ActiveCfg = Debug|Any CPU
{3984651C-FD44-4394-8793-3D14EE348C04}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3984651C-FD44-4394-8793-3D14EE348C04}.Release|Any CPU.Build.0 = Release|Any CPU
{3984651C-FD44-4394-8793-3D14EE348C04}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{3984651C-FD44-4394-8793-3D14EE348C04}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{3984651C-FD44-4394-8793-3D14EE348C04}.Release|x86.ActiveCfg = Release|Any CPU
{8C73D216-332D-41D8-BFD0-45BC4BC36552}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C73D216-332D-41D8-BFD0-45BC4BC36552}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C73D216-332D-41D8-BFD0-45BC4BC36552}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{8C73D216-332D-41D8-BFD0-45BC4BC36552}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{8C73D216-332D-41D8-BFD0-45BC4BC36552}.Debug|x86.ActiveCfg = Debug|Any CPU
{8C73D216-332D-41D8-BFD0-45BC4BC36552}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C73D216-332D-41D8-BFD0-45BC4BC36552}.Release|Any CPU.Build.0 = Release|Any CPU
{8C73D216-332D-41D8-BFD0-45BC4BC36552}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{8C73D216-332D-41D8-BFD0-45BC4BC36552}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{8C73D216-332D-41D8-BFD0-45BC4BC36552}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -82,5 +105,7 @@ Global
{15F1211B-B695-4A1C-B730-1AC58FC91090} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
{558C2C2A-AED8-49DE-BB60-D5F8AE06C714} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
{8DA26CD1-1302-4CFD-9270-9FA1B7C6138B} = {7BF11F3A-60B6-4796-B504-579C67FFBA34}
{3984651C-FD44-4394-8793-3D14EE348C04} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
{8C73D216-332D-41D8-BFD0-45BC4BC36552} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,33 @@
{
"dependencies": {
"Microsoft.AspNet.Hosting": "1.0.0-*",
"Microsoft.AspNet.Http": "1.0.0-*",
"Microsoft.AspNet.Diagnostics": "1.0.0-*",
"Microsoft.AspNet.Security": "1.0.0-*",
"Microsoft.AspNet.Security.Cookies": "1.0.0-*",
"Microsoft.AspNet.Security.Facebook": "1.0.0-*",
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
"Microsoft.Framework.DependencyInjection": "1.0.0-*"
},
"commands": { "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:12345" },
"frameworks": {
"net45": {
},
"k10": {
"dependencies": {
"System.Collections": "4.0.10.0",
"System.Console": "4.0.0.0",
"System.Diagnostics.Debug": "4.0.10.0",
"System.Diagnostics.Tools": "4.0.0.0",
"System.Globalization": "4.0.10.0",
"System.IO": "4.0.0.0",
"System.Linq": "4.0.0.0",
"System.Resources.ResourceManager": "4.0.0.0",
"System.Runtime": "4.0.20.0",
"System.Runtime.Extensions": "4.0.10.0",
"System.Runtime.InteropServices": "4.0.20.0",
"System.Threading.Tasks": "4.0.10.0"
}
}
}
}

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.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>8c73d216-332d-41d8-bfd0-45bc4bc36552</ProjectGuid>
<OutputType>Library</OutputType>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Console'">
<DebuggerFlavor>ConsoleDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="$(OutputType) == 'Web'">
<DebuggerFlavor>WebDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Content Include="Project.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="Startup.cs" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,39 @@
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Security.Cookies;
using Microsoft.AspNet.Security.Facebook;
namespace CookieSample
{
public class Startup
{
public void Configure(IBuilder app)
{
app.UseErrorPage();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
});
app.UseFacebookAuthentication(new FacebookAuthenticationOptions()
{
AppId = "569522623154478",
AppSecret = "a124463c4719c94b4228d9a240e5dc1a",
});
app.Run(async context =>
{
if (!context.User.Identity.IsAuthenticated)
{
context.Response.Challenge("Facebook");
return;
}
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Hello " + context.User.Identity.Name);
});
}
}
}

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 Microsoft.AspNet.Http;
using Microsoft.AspNet.Security.Cookies;
using Microsoft.AspNet.Security.DataProtection;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Builder
{

View File

@ -6,7 +6,7 @@
"Microsoft.AspNet.HttpFeature": "1.0.0-*",
"Microsoft.AspNet.PipelineCore": "1.0.0-*",
"Microsoft.AspNet.RequestContainer": "1.0.0-*",
"Microsoft.AspNet.Security": "",
"Microsoft.AspNet.Security": "1.0.0-*",
"Microsoft.AspNet.Security.DataProtection": "1.0.0-*",
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
"Microsoft.Framework.Logging": "1.0.0-*",

View File

@ -0,0 +1,10 @@
// 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.Facebook
{
public static class FacebookAuthenticationDefaults
{
public const string AuthenticationType = "Facebook";
}
}

View File

@ -0,0 +1,44 @@
// 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.Facebook;
namespace Microsoft.AspNet.Builder
{
/// <summary>
/// Extension methods for using <see cref="FacebookAuthenticationMiddleware"/>
/// </summary>
public static class FacebookAuthenticationExtensions
{
/// <summary>
/// Authenticate users using Facebook
/// </summary>
/// <param name="app">The <see cref="IBuilder"/> passed to the configure method</param>
/// <param name="appId">The appId assigned by Facebook</param>
/// <param name="appSecret">The appSecret assigned by Facebook</param>
/// <returns>The updated <see cref="IBuilder"/></returns>
public static IBuilder UseFacebookAuthentication([NotNull] this IBuilder app, [NotNull] string appId, [NotNull] string appSecret)
{
return app.UseFacebookAuthentication(new FacebookAuthenticationOptions()
{
AppId = appId,
AppSecret = appSecret,
});
}
/// <summary>
/// Authenticate users using Facebook
/// </summary>
/// <param name="app">The <see cref="IBuilder"/> passed to the configure method</param>
/// <param name="options">Middleware configuration options</param>
/// <returns>The updated <see cref="IBuilder"/></returns>
public static IBuilder UseFacebookAuthentication([NotNull] this IBuilder app, [NotNull] FacebookAuthenticationOptions options)
{
if (string.IsNullOrEmpty(options.SignInAsAuthenticationType))
{
options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType();
}
return app.UseMiddleware<FacebookAuthenticationMiddleware>(options);
}
}
}

View File

@ -0,0 +1,293 @@
// 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.Net.Http;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Security;
using Microsoft.AspNet.Security.Infrastructure;
using Microsoft.AspNet.WebUtilities;
using Microsoft.Framework.Logging;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.Security.Facebook
{
internal class FacebookAuthenticationHandler : AuthenticationHandler<FacebookAuthenticationOptions>
{
private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
private const string TokenEndpoint = "https://graph.facebook.com/oauth/access_token";
private const string GraphApiEndpoint = "https://graph.facebook.com/me";
private const string AuthorizationEndpoint = "https://www.facebook.com/dialog/oauth";
private readonly ILogger _logger;
private readonly HttpClient _httpClient;
public FacebookAuthenticationHandler(HttpClient httpClient, ILogger logger)
{
_httpClient = httpClient;
_logger = logger;
}
protected override AuthenticationTicket AuthenticateCore()
{
return AuthenticateCoreAsync().Result;
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
AuthenticationProperties properties = null;
try
{
string code = null;
string state = null;
IReadableStringCollection query = Request.Query;
IList<string> values = query.GetValues("error");
if (values != null && values.Count >= 1)
{
_logger.WriteVerbose("Remote server returned an error: " + Request.QueryString);
}
values = query.GetValues("code");
if (values != null && values.Count == 1)
{
code = values[0];
}
values = query.GetValues("state");
if (values != null && values.Count == 1)
{
state = values[0];
}
properties = Options.StateDataFormat.Unprotect(state);
if (properties == null)
{
return null;
}
// OAuth2 10.12 CSRF
if (!ValidateCorrelationId(properties, _logger))
{
return new AuthenticationTicket(null, properties);
}
if (code == null)
{
// Null if the remote server returns an error.
return new AuthenticationTicket(null, properties);
}
string requestPrefix = Request.Scheme + "://" + Request.Host;
string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath;
string tokenRequest = "grant_type=authorization_code" +
"&code=" + Uri.EscapeDataString(code) +
"&redirect_uri=" + Uri.EscapeDataString(redirectUri) +
"&client_id=" + Uri.EscapeDataString(Options.AppId) +
"&client_secret=" + Uri.EscapeDataString(Options.AppSecret);
var tokenResponse = await _httpClient.GetAsync(TokenEndpoint + "?" + tokenRequest, Context.RequestAborted);
tokenResponse.EnsureSuccessStatusCode();
string text = await tokenResponse.Content.ReadAsStringAsync();
IFormCollection form = FormHelpers.ParseForm(text);
string accessToken = form["access_token"];
string expires = form["expires"];
string graphAddress = GraphApiEndpoint + "?access_token=" + Uri.EscapeDataString(accessToken);
if (Options.SendAppSecretProof)
{
graphAddress += "&appsecret_proof=" + GenerateAppSecretProof(accessToken);
}
var graphResponse = await _httpClient.GetAsync(graphAddress, Context.RequestAborted);
graphResponse.EnsureSuccessStatusCode();
text = await graphResponse.Content.ReadAsStringAsync();
JObject user = JObject.Parse(text);
var context = new FacebookAuthenticatedContext(Context, user, accessToken, expires);
context.Identity = new ClaimsIdentity(
Options.AuthenticationType,
ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType);
if (!string.IsNullOrEmpty(context.Id))
{
context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.UserName))
{
context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.UserName, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Email))
{
context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, Options.AuthenticationType));
}
if (!string.IsNullOrEmpty(context.Name))
{
context.Identity.AddClaim(new Claim("urn:facebook:name", context.Name, XmlSchemaString, Options.AuthenticationType));
// Many Facebook accounts do not set the UserName field. Fall back to the Name field instead.
if (string.IsNullOrEmpty(context.UserName))
{
context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, XmlSchemaString, Options.AuthenticationType));
}
}
if (!string.IsNullOrEmpty(context.Link))
{
context.Identity.AddClaim(new Claim("urn:facebook:link", context.Link, XmlSchemaString, Options.AuthenticationType));
}
context.Properties = properties;
await Options.Notifications.Authenticated(context);
return new AuthenticationTicket(context.Identity, context.Properties);
}
catch (Exception ex)
{
_logger.WriteError("Authentication failed", ex);
return new AuthenticationTicket(null, properties);
}
}
protected override void ApplyResponseChallenge()
{
if (Response.StatusCode != 401)
{
return;
}
// Active middleware should redirect on 401 even if there wasn't an explicit challenge.
if (ChallengeContext == null && Options.AuthenticationMode == AuthenticationMode.Passive)
{
return;
}
string baseUri =
Request.Scheme +
"://" +
Request.Host +
Request.PathBase;
string currentUri =
baseUri +
Request.Path +
Request.QueryString;
string redirectUri =
baseUri +
Options.CallbackPath;
AuthenticationProperties properties;
if (ChallengeContext == null)
{
properties = new AuthenticationProperties();
}
else
{
properties = new AuthenticationProperties(ChallengeContext.Properties);
}
if (string.IsNullOrEmpty(properties.RedirectUri))
{
properties.RedirectUri = currentUri;
}
// OAuth2 10.12 CSRF
GenerateCorrelationId(properties);
// comma separated
string scope = string.Join(",", Options.Scope);
string state = Options.StateDataFormat.Protect(properties);
string authorizationEndpoint =
AuthorizationEndpoint +
"?response_type=code" +
"&client_id=" + Uri.EscapeDataString(Options.AppId) +
"&redirect_uri=" + Uri.EscapeDataString(redirectUri) +
"&scope=" + Uri.EscapeDataString(scope) +
"&state=" + Uri.EscapeDataString(state);
var redirectContext = new FacebookApplyRedirectContext(Context, Options, properties, authorizationEndpoint);
Options.Notifications.ApplyRedirect(redirectContext);
}
protected override void ApplyResponseGrant()
{
// N/A
}
public override async Task<bool> InvokeAsync()
{
return await InvokeReplyPathAsync();
}
private async Task<bool> InvokeReplyPathAsync()
{
if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path)
{
// TODO: error responses
AuthenticationTicket ticket = await AuthenticateAsync();
if (ticket == null)
{
_logger.WriteWarning("Invalid return state, unable to redirect.");
Response.StatusCode = 500;
return true;
}
var context = new FacebookReturnEndpointContext(Context, ticket);
context.SignInAsAuthenticationType = Options.SignInAsAuthenticationType;
context.RedirectUri = ticket.Properties.RedirectUri;
await Options.Notifications.ReturnEndpoint(context);
if (context.SignInAsAuthenticationType != null &&
context.Identity != null)
{
ClaimsIdentity grantIdentity = context.Identity;
if (!string.Equals(grantIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal))
{
grantIdentity = new ClaimsIdentity(grantIdentity.Claims, context.SignInAsAuthenticationType, grantIdentity.NameClaimType, grantIdentity.RoleClaimType);
}
Context.Response.SignIn(context.Properties, grantIdentity);
}
if (!context.IsRequestCompleted && context.RedirectUri != null)
{
string redirectUri = context.RedirectUri;
if (context.Identity == null)
{
// add a redirect hint that sign-in failed in some way
redirectUri = QueryHelpers.AddQueryString(redirectUri, "error", "access_denied");
}
Response.Redirect(redirectUri);
context.RequestCompleted();
}
return context.IsRequestCompleted;
}
return false;
}
private string GenerateAppSecretProof(string accessToken)
{
using (HMACSHA256 algorithm = new HMACSHA256(Encoding.ASCII.GetBytes(Options.AppSecret)))
{
byte[] hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(accessToken));
StringBuilder builder = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
builder.Append(hash[i].ToString("x2", CultureInfo.InvariantCulture));
}
return builder.ToString();
}
}
}
}

View File

@ -0,0 +1,97 @@
// 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;
namespace Microsoft.AspNet.Security.Facebook
{
/// <summary>
/// ASP.NET middleware for authenticating users using Facebook
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Middleware is not disposable.")]
public class FacebookAuthenticationMiddleware : AuthenticationMiddleware<FacebookAuthenticationOptions>
{
private readonly ILogger _logger;
private readonly HttpClient _httpClient;
/// <summary>
/// Initializes a <see cref="FacebookAuthenticationMiddleware"/>
/// </summary>
/// <param name="next">The next middleware in the application pipeline to invoke</param>
/// <param name="options">Configuration options for the middleware</param>
public FacebookAuthenticationMiddleware(
RequestDelegate next,
IDataProtectionProvider dataProtectionProvider,
ILoggerFactory loggerFactory,
IServiceProvider services,
FacebookAuthenticationOptions options)
: base(next, options)
{
if (string.IsNullOrWhiteSpace(Options.AppId))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "AppId"));
}
if (string.IsNullOrWhiteSpace(Options.AppSecret))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "AppSecret"));
}
_logger = loggerFactory.Create(typeof(FacebookAuthenticationMiddleware).FullName);
if (Options.Notifications == null)
{
Options.Notifications = new FacebookAuthenticationNotifications();
}
if (Options.StateDataFormat == null)
{
IDataProtector dataProtector = DataProtectionHelpers.CreateDataProtector(dataProtectionProvider,
typeof(FacebookAuthenticationMiddleware).FullName, options.AuthenticationType, "v1");
Options.StateDataFormat = new PropertiesDataFormat(dataProtector);
}
_httpClient = new HttpClient(ResolveHttpMessageHandler(Options));
_httpClient.Timeout = Options.BackchannelTimeout;
_httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB
}
/// <summary>
/// Provides the <see cref="AuthenticationHandler"/> object for processing authentication-related requests.
/// </summary>
/// <returns>An <see cref="AuthenticationHandler"/> configured with the <see cref="FacebookAuthenticationOptions"/> supplied to the constructor.</returns>
protected override AuthenticationHandler<FacebookAuthenticationOptions> CreateHandler()
{
return new FacebookAuthenticationHandler(_httpClient, _logger);
}
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")]
private static HttpMessageHandler ResolveHttpMessageHandler(FacebookAuthenticationOptions options)
{
HttpMessageHandler handler = options.BackchannelHttpHandler ??
#if NET45
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,112 @@
// 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.Net.Http;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Security;
namespace Microsoft.AspNet.Security.Facebook
{
/// <summary>
/// Configuration options for <see cref="FacebookAuthenticationMiddleware"/>
/// </summary>
public class FacebookAuthenticationOptions : AuthenticationOptions
{
/// <summary>
/// Initializes a new <see cref="FacebookAuthenticationOptions"/>
/// </summary>
[SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters",
MessageId = "Microsoft.AspNet.Security.Facebook.FacebookAuthenticationOptions.set_Caption(System.String)", Justification = "Not localizable.")]
public FacebookAuthenticationOptions()
: base(FacebookAuthenticationDefaults.AuthenticationType)
{
Caption = FacebookAuthenticationDefaults.AuthenticationType;
CallbackPath = new PathString("/signin-facebook");
AuthenticationMode = AuthenticationMode.Passive;
Scope = new List<string>();
BackchannelTimeout = TimeSpan.FromSeconds(60);
SendAppSecretProof = true;
}
/// <summary>
/// Gets or sets the Facebook-assigned appId
/// </summary>
public string AppId { get; set; }
/// <summary>
/// Gets or sets the Facebook-assigned app secret
/// </summary>
public string AppSecret { get; set; }
#if NET45
/// <summary>
/// Gets or sets the a pinned certificate validator to use to validate the endpoints used
/// in back channel communications belong to Facebook.
/// </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>
/// Gets or sets timeout value in milliseconds for back channel communications with Facebook.
/// </summary>
/// <value>
/// The back channel timeout in milliseconds.
/// </value>
public TimeSpan BackchannelTimeout { get; set; }
/// <summary>
/// The HttpMessageHandler used to communicate with Facebook.
/// This cannot be set at the same time as BackchannelCertificateValidator unless the value
/// can be downcast to a WebRequestHandler.
/// </summary>
public HttpMessageHandler BackchannelHttpHandler { get; set; }
/// <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>
/// The request path within the application's base path where the user-agent will be returned.
/// The middleware will process this request when it arrives.
/// Default value is "/signin-facebook".
/// </summary>
public PathString CallbackPath { get; set; }
/// <summary>
/// Gets or sets the name of another authentication middleware which will be responsible for actually issuing a user <see cref="System.Security.Claims.ClaimsIdentity"/>.
/// </summary>
public string SignInAsAuthenticationType { get; set; }
/// <summary>
/// Gets or sets the <see cref="IFacebookAuthenticationNotifications"/> used to handle authentication events.
/// </summary>
public IFacebookAuthenticationNotifications Notifications { get; set; }
/// <summary>
/// Gets or sets the type used to secure data handled by the middleware.
/// </summary>
public ISecureDataFormat<AuthenticationProperties> StateDataFormat { get; set; }
/// <summary>
/// A list of permissions to request.
/// </summary>
public IList<string> Scope { get; private set; }
/// <summary>
/// Gets or sets if the appsecret_proof should be generated and sent with Facebook API calls.
/// This is enabled by default.
/// </summary>
public bool SendAppSecretProof { get; set; }
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.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>3984651c-fd44-4394-8793-3d14ee348c04</ProjectGuid>
<OutputType>Library</OutputType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</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.Facebook
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
internal sealed class NotNullAttribute : Attribute
{
}
}

View File

@ -0,0 +1,43 @@
// 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.Http.Security;
using Microsoft.AspNet.Security.Notifications;
namespace Microsoft.AspNet.Security.Facebook
{
/// <summary>
/// Context passed when a Challenge causes a redirect to authorize endpoint in the Facebook middleware
/// </summary>
public class FacebookApplyRedirectContext : BaseContext<FacebookAuthenticationOptions>
{
/// <summary>
/// Creates a new context object.
/// </summary>
/// <param name="context">The http request context</param>
/// <param name="options">The Facebook middleware options</param>
/// <param name="properties">The authenticaiton properties of the challenge</param>
/// <param name="redirectUri">The initial redirect URI</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "3#",
Justification = "Represents header value")]
public FacebookApplyRedirectContext(HttpContext context, FacebookAuthenticationOptions options,
AuthenticationProperties properties, string redirectUri)
: base(context, options)
{
RedirectUri = redirectUri;
Properties = properties;
}
/// <summary>
/// Gets the URI used for the redirect operation.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Represents header value")]
public string RedirectUri { get; private set; }
/// <summary>
/// Gets the authentication properties of the challenge
/// </summary>
public AuthenticationProperties Properties { get; private set; }
}
}

View File

@ -0,0 +1,98 @@
// 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.Security.Claims;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Security;
using Microsoft.AspNet.Security.Notifications;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.Security.Facebook
{
/// <summary>
/// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.
/// </summary>
public class FacebookAuthenticatedContext : BaseContext
{
/// <summary>
/// Initializes a <see cref="FacebookAuthenticatedContext"/>
/// </summary>
/// <param name="context">The http environment</param>
/// <param name="user">The JSON-serialized user</param>
/// <param name="accessToken">Facebook Access token</param>
/// <param name="expires">Seconds until expiration</param>
public FacebookAuthenticatedContext(HttpContext context, JObject user, string accessToken, string expires)
: base(context)
{
User = user;
AccessToken = accessToken;
int expiresValue;
if (Int32.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue))
{
ExpiresIn = TimeSpan.FromSeconds(expiresValue);
}
Id = TryGetValue(user, "id");
Name = TryGetValue(user, "name");
Link = TryGetValue(user, "link");
UserName = TryGetValue(user, "username");
Email = TryGetValue(user, "email");
}
/// <summary>
/// Gets the JSON-serialized user
/// </summary>
public JObject User { get; private set; }
/// <summary>
/// Gets the Facebook access token
/// </summary>
public string AccessToken { get; private set; }
/// <summary>
/// Gets the Facebook access token expiration time
/// </summary>
public TimeSpan? ExpiresIn { get; set; }
/// <summary>
/// Gets the Facebook user ID
/// </summary>
public string Id { get; private set; }
/// <summary>
/// Gets the user's name
/// </summary>
public string Name { get; private set; }
public string Link { get; private set; }
/// <summary>
/// Gets the Facebook username
/// </summary>
public string UserName { get; private set; }
/// <summary>
/// Gets the Facebook email
/// </summary>
public string Email { get; private set; }
/// <summary>
/// Gets the <see cref="ClaimsIdentity"/> representing the user
/// </summary>
public ClaimsIdentity Identity { get; set; }
/// <summary>
/// Gets or sets a property bag for common authentication properties
/// </summary>
public AuthenticationProperties Properties { get; set; }
private static string TryGetValue(JObject user, string propertyName)
{
JToken value;
return user.TryGetValue(propertyName, out value) ? value.ToString() : null;
}
}
}

View File

@ -0,0 +1,69 @@
// 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.Facebook
{
/// <summary>
/// Default <see cref="IFacebookAuthenticationProvider"/> implementation.
/// </summary>
public class FacebookAuthenticationNotifications : IFacebookAuthenticationNotifications
{
/// <summary>
/// Initializes a <see cref="FacebookAuthenticationNotifications"/>
/// </summary>
public FacebookAuthenticationNotifications()
{
OnAuthenticated = context => Task.FromResult<object>(null);
OnReturnEndpoint = context => Task.FromResult<object>(null);
OnApplyRedirect = context =>
context.Response.Redirect(context.RedirectUri);
}
/// <summary>
/// Gets or sets the function that is invoked when the Authenticated method is invoked.
/// </summary>
public Func<FacebookAuthenticatedContext, Task> OnAuthenticated { get; set; }
/// <summary>
/// Gets or sets the function that is invoked when the ReturnEndpoint method is invoked.
/// </summary>
public Func<FacebookReturnEndpointContext, Task> OnReturnEndpoint { get; set; }
/// <summary>
/// Gets or sets the delegate that is invoked when the ApplyRedirect method is invoked.
/// </summary>
public Action<FacebookApplyRedirectContext> OnApplyRedirect { get; set; }
/// <summary>
/// Invoked whenever Facebook succesfully authenticates a user
/// </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>
public virtual Task Authenticated(FacebookAuthenticatedContext context)
{
return OnAuthenticated(context);
}
/// <summary>
/// Invoked prior to the <see cref="System.Security.Claims.ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.
/// </summary>
/// <param name="context"></param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task ReturnEndpoint(FacebookReturnEndpointContext context)
{
return OnReturnEndpoint(context);
}
/// <summary>
/// Called when a Challenge causes a redirect to authorize endpoint in the Facebook middleware
/// </summary>
/// <param name="context">Contains redirect URI and <see cref="AuthenticationProperties"/> of the challenge </param>
public virtual void ApplyRedirect(FacebookApplyRedirectContext context)
{
OnApplyRedirect(context);
}
}
}

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 Microsoft.AspNet.Http;
using Microsoft.AspNet.Security.Notifications;
namespace Microsoft.AspNet.Security.Facebook
{
/// <summary>
/// Provides context information to middleware providers.
/// </summary>
public class FacebookReturnEndpointContext : ReturnEndpointContext
{
/// <summary>
/// Creates a new context object.
/// </summary>
/// <param name="context">The http environment</param>
/// <param name="ticket">The authentication ticket</param>
public FacebookReturnEndpointContext(
HttpContext context,
AuthenticationTicket ticket)
: base(context, ticket)
{
}
}
}

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.Threading.Tasks;
namespace Microsoft.AspNet.Security.Facebook
{
/// <summary>
/// Specifies callback methods which the <see cref="FacebookAuthenticationMiddleware"></see> invokes to enable developer control over the authentication process. />
/// </summary>
public interface IFacebookAuthenticationNotifications
{
/// <summary>
/// Invoked whenever Facebook succesfully authenticates a user
/// </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 Authenticated(FacebookAuthenticatedContext context);
/// <summary>
/// Invoked prior to the <see cref="System.Security.Claims.ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.
/// </summary>
/// <param name="context"></param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task ReturnEndpoint(FacebookReturnEndpointContext context);
/// <summary>
/// Called when a Challenge causes a redirect to authorize endpoint in the Facebook middleware
/// </summary>
/// <param name="context">Contains redirect URI and <see cref="AuthenticationProperties"/> of the challenge </param>
void ApplyRedirect(FacebookApplyRedirectContext context);
}
}

View File

@ -0,0 +1,44 @@
{
"version": "1.0.0-*",
"dependencies": {
"Microsoft.AspNet.Http": "1.0.0-*",
"Microsoft.AspNet.RequestContainer": "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": "5.0.8",
"System.Net.Http": "4.0.0.0"
},
"frameworks": {
"net45": {
"dependencies": {
"System.Net.Http.WebRequest": ""
}
},
"k10": {
"dependencies": {
"System.Collections": "4.0.10.0",
"System.ComponentModel": "4.0.0.0",
"System.Console": "4.0.0.0",
"System.Diagnostics.Debug": "4.0.10.0",
"System.Diagnostics.Tools": "4.0.0.0",
"System.Globalization": "4.0.10.0",
"System.IO": "4.0.0.0",
"System.IO.Compression": "4.0.0.0",
"System.Linq": "4.0.0.0",
"System.Net.Http.WinHttpHandler": "4.0.0.0",
"System.Reflection": "4.0.10.0",
"System.Resources.ResourceManager": "4.0.0.0",
"System.Runtime": "4.0.20.0",
"System.Runtime.Extensions": "4.0.10.0",
"System.Runtime.InteropServices": "4.0.20.0",
"System.Security.Claims": "1.0.0-*",
"System.Security.Cryptography.Hashing.Algorithms": "4.0.0.0",
"System.Security.Principal": "4.0.0.0",
"System.Threading": "4.0.0.0",
"System.Threading.Tasks": "4.0.10.0"
}
}
}
}

View File

@ -0,0 +1,81 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.32559
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.AspNet.Security.Facebook {
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.Facebook.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

@ -1,16 +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.
/* TODO:
using System;
using Owin;
using Microsoft.AspNet.Security;
namespace Microsoft.AspNet.Security
namespace Microsoft.AspNet.Builder
{
/// <summary>
/// Provides extensions methods for app.Property values that are only needed by implementations of authentication middleware.
/// </summary>
public static class AppBuilderSecurityExtensions
public static class BuilderSecurityExtensions
{
/// <summary>
/// Returns the previously set AuthenticationType that external sign in middleware should use when the
@ -18,7 +17,7 @@ namespace Microsoft.AspNet.Security
/// </summary>
/// <param name="app">App builder passed to the application startup code</param>
/// <returns></returns>
public static string GetDefaultSignInAsAuthenticationType([NotNull] this IAppBuilder app)
public static string GetDefaultSignInAsAuthenticationType([NotNull] this IBuilder app)
{
object value;
if (app.Properties.TryGetValue(Constants.DefaultSignInAsAuthenticationType, out value))
@ -38,10 +37,9 @@ namespace Microsoft.AspNet.Security
/// </summary>
/// <param name="app">App builder passed to the application startup code</param>
/// <param name="authenticationType">AuthenticationType that external middleware should sign in as.</param>
public static void SetDefaultSignInAsAuthenticationType([NotNull] this IAppBuilder app, [NotNull] string authenticationType)
public static void SetDefaultSignInAsAuthenticationType([NotNull] this IBuilder app, [NotNull] string authenticationType)
{
app.Properties[Constants.DefaultSignInAsAuthenticationType] = authenticationType;
}
}
}
*/
}

View File

@ -12,6 +12,6 @@ namespace Microsoft.AspNet.Security
/// <summary>
/// Used by middleware extension methods to coordinate the default value Options property SignInAsAuthenticationType
/// </summary>
public const string DefaultSignInAsAuthenticationType = "Microsoft.AspNet.Security.Constants.DefaultSignInAsAuthenticationType";
internal const string DefaultSignInAsAuthenticationType = "Microsoft.AspNet.Security.DefaultSignInAsAuthenticationType";
}
}

View File

@ -1,7 +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 Microsoft.AspNet.Http.Security;
using Microsoft.AspNet.Security.DataHandler.Encoder;
using Microsoft.AspNet.Security.DataHandler.Serializer;

View File

@ -0,0 +1,124 @@
// 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.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Security;
using Microsoft.AspNet.Security.Cookies;
using Microsoft.AspNet.TestHost;
using Microsoft.Framework.DependencyInjection;
using Shouldly;
using Xunit;
namespace Microsoft.AspNet.Security.Facebook
{
public class FacebookMiddlewareTests
{
[Fact]
public async Task ChallengeWillTriggerApplyRedirectEvent()
{
var options = new FacebookAuthenticationOptions()
{
AppId = "Test App Id",
AppSecret = "Test App Secret",
Notifications = new FacebookAuthenticationNotifications
{
OnApplyRedirect = context =>
{
context.Response.Redirect(context.RedirectUri + "&custom=test");
}
}
};
var server = CreateServer(
app => app.UseFacebookAuthentication(options),
context =>
{
context.Response.Challenge("Facebook");
return true;
});
var transaction = await SendAsync(server, "http://example.com/challenge");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
var query = transaction.Response.Headers.Location.Query;
query.ShouldContain("custom=test");
}
[Fact]
public async Task ChallengeWillTriggerRedirection()
{
var server = CreateServer(
app => app.UseFacebookAuthentication("Test App Id", "Test App Secret"),
context =>
{
context.Response.Challenge("Facebook");
return true;
});
var transaction = await SendAsync(server, "http://example.com/challenge");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
var location = transaction.Response.Headers.Location.AbsoluteUri;
location.ShouldContain("https://www.facebook.com/dialog/oauth");
location.ShouldContain("?response_type=code");
location.ShouldContain("&client_id=");
location.ShouldContain("&redirect_uri=");
location.ShouldContain("&scope=");
location.ShouldContain("&state=");
}
private static TestServer CreateServer(Action<IBuilder> configure, Func<HttpContext, bool> handler)
{
return TestServer.Create(app =>
{
app.SetDefaultSignInAsAuthenticationType("External");
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = "External"
});
if (configure != null)
{
configure(app);
}
app.Use(async (context, next) =>
{
if (handler == null || !handler(context))
{
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();
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; }
}
}
}

View File

@ -3,9 +3,12 @@
"warningsAsErrors": true
},
"dependencies": {
"Microsoft.AspNet.Http": "1.0.0-*",
"Microsoft.AspNet.Security" : "",
"Microsoft.AspNet.Security.Cookies" : "",
"Microsoft.AspNet.Security.Facebook" : "",
"Microsoft.AspNet.TestHost": "1.0.0-*",
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
"System.Net.Http": "4.0.0.0",
"Moq": "4.2.1312.1622",
"Xunit.KRunner": "1.0.0-*"