From 8babcbcd29ab5f40df978b28c5d334fd6c5d1f9f Mon Sep 17 00:00:00 2001 From: Jerrie Pelser Date: Mon, 3 Nov 2014 13:23:44 +0700 Subject: [PATCH] First draft of WordPress provider in place --- .../Owin.Security.Providers.csproj | 9 + ...Owin.Security.Providers.csproj.DotSettings | 3 +- .../WordPress/Constants.cs | 7 + .../IWordPressAuthenticationProvider.cs | 24 ++ .../Provider/WordPressAuthenticatedContext.cs | 90 +++++++ .../WordPressAuthenticationProvider.cs | 50 ++++ .../WordPressReturnEndpointContext.cs | 26 ++ .../WordPressAuthenticationExtensions.cs | 29 +++ .../WordPressAuthenticationHandler.cs | 228 ++++++++++++++++++ .../WordPressAuthenticationMiddleware.cs | 85 +++++++ .../WordPressAuthenticationOptions.cs | 92 +++++++ ...-OwinOAuthProvidersDemo-20131113093838.mdf | Bin 3211264 -> 3211264 bytes ...nOAuthProvidersDemo-20131113093838_log.ldf | Bin 1048576 -> 1048576 bytes .../App_Start/Startup.Auth.cs | 5 + 14 files changed, 647 insertions(+), 1 deletion(-) create mode 100644 Owin.Security.Providers/WordPress/Constants.cs create mode 100644 Owin.Security.Providers/WordPress/Provider/IWordPressAuthenticationProvider.cs create mode 100644 Owin.Security.Providers/WordPress/Provider/WordPressAuthenticatedContext.cs create mode 100644 Owin.Security.Providers/WordPress/Provider/WordPressAuthenticationProvider.cs create mode 100644 Owin.Security.Providers/WordPress/Provider/WordPressReturnEndpointContext.cs create mode 100644 Owin.Security.Providers/WordPress/WordPressAuthenticationExtensions.cs create mode 100644 Owin.Security.Providers/WordPress/WordPressAuthenticationHandler.cs create mode 100644 Owin.Security.Providers/WordPress/WordPressAuthenticationMiddleware.cs create mode 100644 Owin.Security.Providers/WordPress/WordPressAuthenticationOptions.cs diff --git a/Owin.Security.Providers/Owin.Security.Providers.csproj b/Owin.Security.Providers/Owin.Security.Providers.csproj index 74cfa76..1782424 100644 --- a/Owin.Security.Providers/Owin.Security.Providers.csproj +++ b/Owin.Security.Providers/Owin.Security.Providers.csproj @@ -179,6 +179,15 @@ + + + + + + + + + diff --git a/Owin.Security.Providers/Owin.Security.Providers.csproj.DotSettings b/Owin.Security.Providers/Owin.Security.Providers.csproj.DotSettings index ff86f88..1882ca9 100644 --- a/Owin.Security.Providers/Owin.Security.Providers.csproj.DotSettings +++ b/Owin.Security.Providers/Owin.Security.Providers.csproj.DotSettings @@ -1,3 +1,4 @@  True - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/Owin.Security.Providers/WordPress/Constants.cs b/Owin.Security.Providers/WordPress/Constants.cs new file mode 100644 index 0000000..21d849c --- /dev/null +++ b/Owin.Security.Providers/WordPress/Constants.cs @@ -0,0 +1,7 @@ +namespace Owin.Security.Providers.WordPress +{ + internal static class Constants + { + public const string DefaultAuthenticationType = "WordPress"; + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/WordPress/Provider/IWordPressAuthenticationProvider.cs b/Owin.Security.Providers/WordPress/Provider/IWordPressAuthenticationProvider.cs new file mode 100644 index 0000000..d04ae07 --- /dev/null +++ b/Owin.Security.Providers/WordPress/Provider/IWordPressAuthenticationProvider.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; + +namespace Owin.Security.Providers.WordPress +{ + /// + /// Specifies callback methods which the invokes to enable developer control over the authentication process. /> + /// + public interface IWordPressAuthenticationProvider + { + /// + /// Invoked whenever WordPress succesfully authenticates a user + /// + /// Contains information about the login session as well as the user . + /// A representing the completed operation. + Task Authenticated(WordPressAuthenticatedContext context); + + /// + /// Invoked prior to the being saved in a local cookie and the browser being redirected to the originally requested URL. + /// + /// + /// A representing the completed operation. + Task ReturnEndpoint(WordPressReturnEndpointContext context); + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/WordPress/Provider/WordPressAuthenticatedContext.cs b/Owin.Security.Providers/WordPress/Provider/WordPressAuthenticatedContext.cs new file mode 100644 index 0000000..58bfbe4 --- /dev/null +++ b/Owin.Security.Providers/WordPress/Provider/WordPressAuthenticatedContext.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Security.Claims; +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Provider; +using Newtonsoft.Json.Linq; + +namespace Owin.Security.Providers.WordPress +{ + /// + /// Contains information about the login session as well as the user . + /// + public class WordPressAuthenticatedContext : BaseContext + { + /// + /// Initializes a + /// + /// The OWIN environment + /// The JSON-serialized user + /// WordPress Access token + /// Seconds until expiration + public WordPressAuthenticatedContext(IOwinContext context, JObject user, string accessToken, string expires) + : base(context) + { + User = user; + Id = TryGetValue(user, "ID"); + Name = TryGetValue(user, "display_name"); + Email = TryGetValue(user, "email"); + AccessToken = accessToken; + + int expiresValue; + if (Int32.TryParse(expires, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresValue)) + { + ExpiresIn = TimeSpan.FromSeconds(expiresValue); + } + } + + /// + /// The email address of the user + /// + public string Email { get; set; } + + /// + /// Gets the JSON-serialized user + /// + /// + /// Contains the WordPress user obtained from the endpoint https://public-api.wordpress.com/rest/v1/me + /// + public JObject User { get; private set; } + + /// + /// Gets the WordPress OAuth access token + /// + public string AccessToken { get; private set; } + + /// + /// Gets the WordPress access token expiration time + /// + public TimeSpan? ExpiresIn { get; set; } + + /// + /// Gets the WordPress user ID + /// + public string Id { get; private set; } + + /// + /// The name of the user + /// + public string Name { get; private set; } + + /// + /// Gets the representing the user + /// + public ClaimsIdentity Identity { get; set; } + + /// + /// Gets or sets a property bag for common authentication properties + /// + public AuthenticationProperties Properties { get; set; } + + private static string TryGetValue(JObject user, string propertyName) + { + JToken value; + return user.TryGetValue(propertyName, out value) ? value.ToString() : null; + } + } +} diff --git a/Owin.Security.Providers/WordPress/Provider/WordPressAuthenticationProvider.cs b/Owin.Security.Providers/WordPress/Provider/WordPressAuthenticationProvider.cs new file mode 100644 index 0000000..175f861 --- /dev/null +++ b/Owin.Security.Providers/WordPress/Provider/WordPressAuthenticationProvider.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading.Tasks; + +namespace Owin.Security.Providers.WordPress +{ + /// + /// Default implementation. + /// + public class WordPressAuthenticationProvider : IWordPressAuthenticationProvider + { + /// + /// Initializes a + /// + public WordPressAuthenticationProvider() + { + OnAuthenticated = context => Task.FromResult(null); + OnReturnEndpoint = context => Task.FromResult(null); + } + + /// + /// Gets or sets the function that is invoked when the Authenticated method is invoked. + /// + public Func OnAuthenticated { get; set; } + + /// + /// Gets or sets the function that is invoked when the ReturnEndpoint method is invoked. + /// + public Func OnReturnEndpoint { get; set; } + + /// + /// Invoked whenever WordPress succesfully authenticates a user + /// + /// Contains information about the login session as well as the user . + /// A representing the completed operation. + public virtual Task Authenticated(WordPressAuthenticatedContext context) + { + return OnAuthenticated(context); + } + + /// + /// Invoked prior to the being saved in a local cookie and the browser being redirected to the originally requested URL. + /// + /// + /// A representing the completed operation. + public virtual Task ReturnEndpoint(WordPressReturnEndpointContext context) + { + return OnReturnEndpoint(context); + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/WordPress/Provider/WordPressReturnEndpointContext.cs b/Owin.Security.Providers/WordPress/Provider/WordPressReturnEndpointContext.cs new file mode 100644 index 0000000..51216c9 --- /dev/null +++ b/Owin.Security.Providers/WordPress/Provider/WordPressReturnEndpointContext.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Provider; + +namespace Owin.Security.Providers.WordPress +{ + /// + /// Provides context information to middleware providers. + /// + public class WordPressReturnEndpointContext : ReturnEndpointContext + { + /// + /// + /// + /// OWIN environment + /// The authentication ticket + public WordPressReturnEndpointContext( + IOwinContext context, + AuthenticationTicket ticket) + : base(context, ticket) + { + } + } +} diff --git a/Owin.Security.Providers/WordPress/WordPressAuthenticationExtensions.cs b/Owin.Security.Providers/WordPress/WordPressAuthenticationExtensions.cs new file mode 100644 index 0000000..85f4f13 --- /dev/null +++ b/Owin.Security.Providers/WordPress/WordPressAuthenticationExtensions.cs @@ -0,0 +1,29 @@ +using System; + +namespace Owin.Security.Providers.WordPress +{ + public static class WordPressAuthenticationExtensions + { + public static IAppBuilder UseWordPressAuthentication(this IAppBuilder app, + WordPressAuthenticationOptions options) + { + if (app == null) + throw new ArgumentNullException("app"); + if (options == null) + throw new ArgumentNullException("options"); + + app.Use(typeof(WordPressAuthenticationMiddleware), app, options); + + return app; + } + + public static IAppBuilder UseWordPressAuthentication(this IAppBuilder app, string clientId, string clientSecret) + { + return app.UseWordPressAuthentication(new WordPressAuthenticationOptions + { + ClientId = clientId, + ClientSecret = clientSecret + }); + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/WordPress/WordPressAuthenticationHandler.cs b/Owin.Security.Providers/WordPress/WordPressAuthenticationHandler.cs new file mode 100644 index 0000000..582f1be --- /dev/null +++ b/Owin.Security.Providers/WordPress/WordPressAuthenticationHandler.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Owin; +using Microsoft.Owin.Infrastructure; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Owin.Security.Providers.WordPress +{ + public class WordPressAuthenticationHandler : AuthenticationHandler + { + private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string"; + private const string TokenEndpoint = "https://public-api.wordpress.com/oauth2/token"; + private const string UserInfoEndpoint = "https://public-api.wordpress.com/rest/v1/me"; + + private readonly ILogger logger; + private readonly HttpClient httpClient; + + public WordPressAuthenticationHandler(HttpClient httpClient, ILogger logger) + { + this.httpClient = httpClient; + this.logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + AuthenticationProperties properties = null; + + try + { + string code = null; + string state = null; + + IReadableStringCollection query = Request.Query; + IList 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); + } + + string requestPrefix = Request.Scheme + "://" + Request.Host; + string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath; + + // Build up the body for the token request + var body = new List>(); + body.Add(new KeyValuePair("grant_type", "authorization_code")); + body.Add(new KeyValuePair("code", code)); + body.Add(new KeyValuePair("redirect_uri", redirectUri)); + body.Add(new KeyValuePair("client_id", Options.ClientId)); + body.Add(new KeyValuePair("client_secret", Options.ClientSecret)); + + // Request the token + HttpResponseMessage tokenResponse = + await httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body)); + tokenResponse.EnsureSuccessStatusCode(); + string text = await tokenResponse.Content.ReadAsStringAsync(); + + // Deserializes the token response + dynamic response = JsonConvert.DeserializeObject(text); + string accessToken = (string)response.access_token; + string expires = (string) response.expires_in; + + // Get the Wordpress user + HttpRequestMessage userRequest = new HttpRequestMessage(HttpMethod.Get, UserInfoEndpoint); + userRequest.Headers.Add("User-Agent", "OWIN OAuth Provider"); + userRequest.Headers.Add("Authorization", "BEARER " + accessToken); + HttpResponseMessage graphResponse = await httpClient.SendAsync(userRequest, Request.CallCancelled); + graphResponse.EnsureSuccessStatusCode(); + text = await graphResponse.Content.ReadAsStringAsync(); + JObject user = JObject.Parse(text); + + var context = new WordPressAuthenticatedContext(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.Name)) + { + context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, XmlSchemaString, Options.AuthenticationType)); + } + if (!string.IsNullOrEmpty(context.Email)) + { + context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, Options.AuthenticationType)); + } + context.Properties = properties; + + await Options.Provider.Authenticated(context); + + return new AuthenticationTicket(context.Identity, context.Properties); + } + catch (Exception ex) + { + logger.WriteError(ex.Message); + } + return new AuthenticationTicket(null, properties); + } + + protected override Task ApplyResponseChallengeAsync() + { + if (Response.StatusCode != 401) + { + return Task.FromResult(null); + } + + AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + + if (challenge != null) + { + string baseUri = + Request.Scheme + + Uri.SchemeDelimiter + + Request.Host + + Request.PathBase; + + string currentUri = + baseUri + + Request.Path + + Request.QueryString; + + string redirectUri = + baseUri + + Options.CallbackPath; + + AuthenticationProperties properties = challenge.Properties; + if (string.IsNullOrEmpty(properties.RedirectUri)) + { + properties.RedirectUri = currentUri; + } + + // OAuth2 10.12 CSRF + GenerateCorrelationId(properties); + + string state = Options.StateDataFormat.Protect(properties); + + string authorizationEndpoint = + "https://public-api.wordpress.com/oauth2/authorize" + + "?response_type=code" + + "&client_id=" + Uri.EscapeDataString(Options.ClientId) + + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + + "&state=" + Uri.EscapeDataString(state); + + Response.Redirect(authorizationEndpoint); + } + + return Task.FromResult(null); + } + + public override async Task InvokeAsync() + { + return await InvokeReplyPathAsync(); + } + + private async Task 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 WordPressReturnEndpointContext(Context, ticket); + context.SignInAsAuthenticationType = Options.SignInAsAuthenticationType; + context.RedirectUri = ticket.Properties.RedirectUri; + + await Options.Provider.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.Authentication.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 = WebUtilities.AddQueryString(redirectUri, "error", "access_denied"); + } + Response.Redirect(redirectUri); + context.RequestCompleted(); + } + + return context.IsRequestCompleted; + } + return false; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/WordPress/WordPressAuthenticationMiddleware.cs b/Owin.Security.Providers/WordPress/WordPressAuthenticationMiddleware.cs new file mode 100644 index 0000000..a891329 --- /dev/null +++ b/Owin.Security.Providers/WordPress/WordPressAuthenticationMiddleware.cs @@ -0,0 +1,85 @@ +using System; +using System.Globalization; +using System.Net.Http; +using Microsoft.Owin; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.DataHandler; +using Microsoft.Owin.Security.DataProtection; +using Microsoft.Owin.Security.Infrastructure; +using Owin.Security.Providers.Properties; + +namespace Owin.Security.Providers.WordPress +{ + public class WordPressAuthenticationMiddleware : AuthenticationMiddleware + { + private readonly HttpClient httpClient; + private readonly ILogger logger; + + public WordPressAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, + WordPressAuthenticationOptions options) + : base(next, options) + { + 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")); + + logger = app.CreateLogger(); + + if (Options.Provider == null) + Options.Provider = new WordPressAuthenticationProvider(); + + if (Options.StateDataFormat == null) + { + IDataProtector dataProtector = app.CreateDataProtector( + typeof (WordPressAuthenticationMiddleware).FullName, + Options.AuthenticationType, "v1"); + Options.StateDataFormat = new PropertiesDataFormat(dataProtector); + } + + if (String.IsNullOrEmpty(Options.SignInAsAuthenticationType)) + Options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(); + + httpClient = new HttpClient(ResolveHttpMessageHandler(Options)) + { + Timeout = Options.BackchannelTimeout, + MaxResponseContentBufferSize = 1024*1024*10 + }; + } + + /// + /// Provides the object for processing + /// authentication-related requests. + /// + /// + /// An configured with the + /// supplied to the constructor. + /// + protected override AuthenticationHandler CreateHandler() + { + return new WordPressAuthenticationHandler(httpClient, logger); + } + + private HttpMessageHandler ResolveHttpMessageHandler(WordPressAuthenticationOptions options) + { + HttpMessageHandler handler = options.BackchannelHttpHandler ?? 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; + } + + return handler; + } + } +} \ No newline at end of file diff --git a/Owin.Security.Providers/WordPress/WordPressAuthenticationOptions.cs b/Owin.Security.Providers/WordPress/WordPressAuthenticationOptions.cs new file mode 100644 index 0000000..be1fe8f --- /dev/null +++ b/Owin.Security.Providers/WordPress/WordPressAuthenticationOptions.cs @@ -0,0 +1,92 @@ +using System; +using System.Net.Http; +using Microsoft.Owin; +using Microsoft.Owin.Security; + +namespace Owin.Security.Providers.WordPress +{ + public class WordPressAuthenticationOptions : AuthenticationOptions + { + /// + /// Gets or sets the a pinned certificate validator to use to validate the endpoints used + /// in back channel communications belong to WordPress + /// + /// + /// The pinned certificate validator. + /// + /// + /// 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. + /// + public ICertificateValidator BackchannelCertificateValidator { get; set; } + + /// + /// The HttpMessageHandler used to communicate with WordPress. + /// This cannot be set at the same time as BackchannelCertificateValidator unless the value + /// can be downcast to a WebRequestHandler. + /// + public HttpMessageHandler BackchannelHttpHandler { get; set; } + + /// + /// Gets or sets timeout value in milliseconds for back channel communications with WordPress. + /// + /// + /// The back channel timeout in milliseconds. + /// + public TimeSpan BackchannelTimeout { get; set; } + + /// + /// 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-wordpress". + /// + public PathString CallbackPath { get; set; } + + /// + /// Get or sets the text that the user can display on a sign in user interface. + /// + public string Caption + { + get { return Description.Caption; } + set { Description.Caption = value; } + } + + /// + /// Gets or sets the WordPress supplied Client ID + /// + public string ClientId { get; set; } + + /// + /// Gets or sets the WordPress supplied Client Secret + /// + public string ClientSecret { get; set; } + + /// + /// Gets or sets the used in the authentication events + /// + public IWordPressAuthenticationProvider Provider { get; set; } + + /// + /// Gets or sets the name of another authentication middleware which will be responsible for actually issuing a user + /// . + /// + public string SignInAsAuthenticationType { get; set; } + + /// + /// Gets or sets the type used to secure data handled by the middleware. + /// + public ISecureDataFormat StateDataFormat { get; set; } + + /// + /// Initializes a new + /// + public WordPressAuthenticationOptions() + : base("WordPress") + { + Caption = Constants.DefaultAuthenticationType; + CallbackPath = new PathString("/signin-wordpress"); + AuthenticationMode = AuthenticationMode.Passive; + BackchannelTimeout = TimeSpan.FromSeconds(60); + } + } +} \ No newline at end of file diff --git a/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838.mdf b/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838.mdf index 59e5849c86541bb539eae5d6cddb997e41ea88df..eca3240e7d9bdbb98c202814dee10116b16f7164 100644 GIT binary patch delta 309 zcmYMrISRs15XN!ebKf=YTYPbeOWaN%9>U7flcckj2wp?6uolE)SXkTI3Hnb!9OlQ& zJice!S7GBI)&BUZ=bfU`gtgx;WG*nDy?4&#+B&V(?NhdVlCNR8d164d`e> c!9WXbbkK!~9{Lzy2n!>OF~RiwFlPGw3%lKB*#H0l delta 323 zcmYk!xedZ#42EI<;l7WA`}z~^n+a&xf|8mc(lJ1mphTjeMnV(}LBSdn@KOY@^vIU| z$*y}*E)IrP%Cm2#SJe6PNJREZBuMvbxo|A+d~uE&eSLisA^%K2Y+wCP;6hA8Q48+1p}Wsh!DbvAc`2`NFa$6(#Rl-9P%(>p@1SvD5HWZYN(@uCR)(2 b;h>EUy6B;g0frc1j0vWgVSakpi{||e_s(aD diff --git a/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838_log.ldf b/OwinOAuthProvidersDemo/App_Data/aspnet-OwinOAuthProvidersDemo-20131113093838_log.ldf index 022d7ec954663b2a538dfde99f537894ed890665..ecc0307d3fc4fd60724981c098e6c7793ec0f96f 100644 GIT binary patch delta 5706 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