diff --git a/Security.sln b/Security.sln index 5a80619fc3..7628374e9f 100644 --- a/Security.sln +++ b/Security.sln @@ -9,6 +9,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Security.k10", "src\Microsoft.AspNet.Security\Microsoft.AspNet.Security.k10.csproj", "{F2391907-4463-4650-AEA4-E1B5733105C4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Security.Cookies.net45", "src\Microsoft.AspNet.Security.Cookies\Microsoft.AspNet.Security.Cookies.net45.csproj", "{4579FEC1-3C4C-4F56-ADDB-850F947FDC2D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Security.Cookies.k10", "src\Microsoft.AspNet.Security.Cookies\Microsoft.AspNet.Security.Cookies.k10.csproj", "{17DD587C-CFB8-407C-A674-1C99F5CE9C7E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CookieSample.net45", "samples\CookieSample\CookieSample.net45.csproj", "{3C25520E-1AF0-4535-A111-6A1C04D31364}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -23,6 +31,18 @@ Global {F2391907-4463-4650-AEA4-E1B5733105C4}.Debug|Any CPU.Build.0 = Debug|Any CPU {F2391907-4463-4650-AEA4-E1B5733105C4}.Release|Any CPU.ActiveCfg = Release|Any CPU {F2391907-4463-4650-AEA4-E1B5733105C4}.Release|Any CPU.Build.0 = Release|Any CPU + {4579FEC1-3C4C-4F56-ADDB-850F947FDC2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4579FEC1-3C4C-4F56-ADDB-850F947FDC2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4579FEC1-3C4C-4F56-ADDB-850F947FDC2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4579FEC1-3C4C-4F56-ADDB-850F947FDC2D}.Release|Any CPU.Build.0 = Release|Any CPU + {17DD587C-CFB8-407C-A674-1C99F5CE9C7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17DD587C-CFB8-407C-A674-1C99F5CE9C7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17DD587C-CFB8-407C-A674-1C99F5CE9C7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17DD587C-CFB8-407C-A674-1C99F5CE9C7E}.Release|Any CPU.Build.0 = Release|Any CPU + {3C25520E-1AF0-4535-A111-6A1C04D31364}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C25520E-1AF0-4535-A111-6A1C04D31364}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C25520E-1AF0-4535-A111-6A1C04D31364}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C25520E-1AF0-4535-A111-6A1C04D31364}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -30,5 +50,8 @@ Global GlobalSection(NestedProjects) = preSolution {70640501-CCBB-4FD7-8231-329B6A436DE4} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652} {F2391907-4463-4650-AEA4-E1B5733105C4} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652} + {4579FEC1-3C4C-4F56-ADDB-850F947FDC2D} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652} + {17DD587C-CFB8-407C-A674-1C99F5CE9C7E} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652} + {3C25520E-1AF0-4535-A111-6A1C04D31364} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF} EndGlobalSection EndGlobal diff --git a/samples/CookieSample/Startup.cs b/samples/CookieSample/Startup.cs new file mode 100644 index 0000000000..9c2c29e185 --- /dev/null +++ b/samples/CookieSample/Startup.cs @@ -0,0 +1,42 @@ +using System; +using System.Linq; +using System.Security.Claims; +using Microsoft.AspNet; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Abstractions.Security; +using Microsoft.AspNet.HttpFeature.Security; +using Microsoft.AspNet.Security; +using Microsoft.AspNet.Security.Cookies; +using Microsoft.AspNet.Security.Infrastructure; + +namespace CookieSample +{ + public class Startup + { + public void Configuration(IBuilder app) + { + Console.WriteLine("Attach"); + Console.ReadKey(); + + app.UseCookieAuthentication(new CookieAuthenticationOptions() + { + + }); + + app.Run(async context => + { + if (context.User == null || !context.User.Identity.IsAuthenticated) + { + context.Authentication.SignIn(new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim("name", "bob") }, CookieAuthenticationDefaults.AuthenticationType))); + + context.Response.ContentType = "text/plain"; + await context.Response.WriteAsync("Hello First timer"); + return; + } + + context.Response.ContentType = "text/plain"; + await context.Response.WriteAsync("Hello old timer"); + }); + } + } +} \ No newline at end of file diff --git a/samples/CookieSample/project.json b/samples/CookieSample/project.json new file mode 100644 index 0000000000..b25b4148d6 --- /dev/null +++ b/samples/CookieSample/project.json @@ -0,0 +1,39 @@ +{ + "version": "0.1-alpha-*", + "dependencies": { + "Microsoft.AspNet.Abstractions": "0.1-alpha-*", + "Microsoft.AspNet.Security": "", + "Microsoft.AspNet.Security.Cookies": "", + "Microsoft.AspNet.Hosting": "0.1-alpha-*", + "Microsoft.AspNet.PipelineCore": "0.1-alpha-*", + "Microsoft.AspNet.Abstractions": "0.1-alpha-*", + "Microsoft.AspNet.FeatureModel": "0.1-alpha-*", + "Microsoft.AspNet.HttpFeature": "0.1-alpha-*", + "Microsoft.AspNet.Server.WebListener": "0.1-alpha-*" + }, + "commands": { "web": "Microsoft.AspNet.Hosting server.name=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:12345" }, + "configurations": { + "net45": { + }, + "k10": { + "dependencies": { + "System.Console": "4.0.0.0", + "System.Collections": "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.FileSystem": "4.0.0.0", + "System.IO.FileSystem.Primitives": "4.0.0.0", + "System.Linq": "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.10.0", + "System.Text.Encoding": "4.0.10.0", + "System.Threading.Tasks": "4.0.0.0" + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationDefaults.cs b/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationDefaults.cs new file mode 100644 index 0000000000..8102911fd2 --- /dev/null +++ b/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationDefaults.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Security.Cookies +{ + /// + /// Default values related to cookie-based authentication middleware + /// + public static class CookieAuthenticationDefaults + { + /// + /// The default value used for CookieAuthenticationOptions.AuthenticationType + /// + public const string AuthenticationType = "Cookies"; + + /// + /// The prefix used to provide a default CookieAuthenticationOptions.CookieName + /// + public const string CookiePrefix = ".AspNet."; + + /// + /// The default value used by UseApplicationSignInCookie for the + /// CookieAuthenticationOptions.LoginPath + /// + [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Justification = "By design")] + public static readonly PathString LoginPath = new PathString("/Account/Login"); + + /// + /// The default value used by UseApplicationSignInCookie for the + /// CookieAuthenticationOptions.LogoutPath + /// + [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Logout", Justification = "By design")] + public static readonly PathString LogoutPath = new PathString("/Account/Logout"); + + /// + /// The default value of the CookieAuthenticationOptions.ReturnUrlParameter + /// + public const string ReturnUrlParameter = "ReturnUrl"; + } +} diff --git a/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationExtensions.cs b/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationExtensions.cs new file mode 100644 index 0000000000..91d926ab95 --- /dev/null +++ b/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationExtensions.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Logging; +using Microsoft.AspNet.Security.Cookies; +using Microsoft.AspNet.Security.DataHandler; +using Microsoft.AspNet.Security.DataProtection; + +namespace Microsoft.AspNet +{ + /// + /// Extension methods provided by the cookies authentication middleware + /// + public static class CookieAuthenticationExtensions + { + /// + /// Adds a cookie-based authentication middleware to your web application pipeline. + /// + /// The IAppBuilder passed to your configuration method + /// An options class that controls the middleware behavior + /// The original app parameter + public static IBuilder UseCookieAuthentication(this IBuilder app, CookieAuthenticationOptions options) + { + if (app == null) + { + throw new ArgumentNullException("app"); + } + /* + // TODO: Extension methods for this? + var loggerFactory = (ILoggerFactory)app.ServiceProvider.GetService(typeof(ILoggerFactory)); + ILogger logger = loggerFactory.Create(typeof(CookieAuthenticationMiddleware).FullName); + */ + ILogger logger = null; + + if (options.TicketDataFormat == null) + { + /* TODO: Add DPP extensions + IDataProtector dataProtector = app.CreateDataProtector( + typeof(CookieAuthenticationMiddleware).FullName, + options.AuthenticationType, "v1"); + */ + var dataProtectionProvider = (IDataProtectionProvider)app.ServiceProvider.GetService(typeof(IDataProtectionProvider)); + IDataProtector dataProtector = dataProtectionProvider.CreateProtector(string.Join(";", typeof(CookieAuthenticationMiddleware).FullName, options.AuthenticationType, "v1")); + options.TicketDataFormat = new TicketDataFormat(dataProtector); + } + + app.Use(next => new CookieAuthenticationMiddleware(next, logger, options).Invoke); + // TODO: ? app.UseStageMarker(PipelineStage.Authenticate); + return app; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationHandler.cs b/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationHandler.cs new file mode 100644 index 0000000000..5378223c14 --- /dev/null +++ b/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationHandler.cs @@ -0,0 +1,249 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.Security.Infrastructure; +using Microsoft.AspNet.Logging; +using Microsoft.AspNet.PipelineCore.Collections; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.HttpFeature.Security; + +namespace Microsoft.AspNet.Security.Cookies +{ + internal class CookieAuthenticationHandler : AuthenticationHandler + { + private const string HeaderNameCacheControl = "Cache-Control"; + private const string HeaderNamePragma = "Pragma"; + private const string HeaderNameExpires = "Expires"; + private const string HeaderValueNoCache = "no-cache"; + private const string HeaderValueMinusOne = "-1"; + + private readonly ILogger _logger; + + private bool _shouldRenew; + private DateTimeOffset _renewIssuedUtc; + private DateTimeOffset _renewExpiresUtc; + + public CookieAuthenticationHandler(ILogger logger) + { + /* + if (logger == null) + { + throw new ArgumentNullException("logger"); + }*/ + _logger = logger; + } + + protected override AuthenticationTicket AuthenticateCore() + { + return AuthenticateCoreAsync().Result; + } + + protected override async Task AuthenticateCoreAsync() + { + IReadableStringCollection cookies = Request.Cookies; + string cookie = cookies[Options.CookieName]; + if (string.IsNullOrWhiteSpace(cookie)) + { + return null; + } + + AuthenticationTicket ticket = Options.TicketDataFormat.Unprotect(cookie); + + if (ticket == null) + { + // TODO: _logger.WriteWarning(@"Unprotect ticket failed"); + return null; + } + + DateTimeOffset currentUtc = Options.SystemClock.UtcNow; + DateTimeOffset? issuedUtc = ticket.Properties.IssuedUtc; + DateTimeOffset? expiresUtc = ticket.Properties.ExpiresUtc; + + if (expiresUtc != null && expiresUtc.Value < currentUtc) + { + return null; + } + + if (issuedUtc != null && expiresUtc != null && Options.SlidingExpiration) + { + TimeSpan timeElapsed = currentUtc.Subtract(issuedUtc.Value); + TimeSpan timeRemaining = expiresUtc.Value.Subtract(currentUtc); + + if (timeRemaining < timeElapsed) + { + _shouldRenew = true; + _renewIssuedUtc = currentUtc; + TimeSpan timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value); + _renewExpiresUtc = currentUtc.Add(timeSpan); + } + } + + var context = new CookieValidateIdentityContext(Context, ticket, Options); + + await Options.Provider.ValidateIdentity(context); + + return new AuthenticationTicket(context.Identity, context.Properties); + } + + protected override void ApplyResponseGrant() + { + ApplyResponseGrantAsync().Wait(); + } + + protected override async Task ApplyResponseGrantAsync() + { + var signin = SignInIdentityContext; + bool shouldSignin = signin != null; + var signout = SignOutContext; + bool shouldSignout = signout != null; + + if (shouldSignin || shouldSignout || _shouldRenew) + { + var cookieOptions = new CookieOptions + { + Domain = Options.CookieDomain, + HttpOnly = Options.CookieHttpOnly, + Path = Options.CookiePath ?? "/", + }; + if (Options.CookieSecure == CookieSecureOption.SameAsRequest) + { + cookieOptions.Secure = Request.IsSecure; + } + else + { + cookieOptions.Secure = Options.CookieSecure == CookieSecureOption.Always; + } + + if (shouldSignin) + { + var context = new CookieResponseSignInContext( + Context, + Options, + Options.AuthenticationType, + signin.Identity, + signin.Properties, + cookieOptions); + + DateTimeOffset issuedUtc = Options.SystemClock.UtcNow; + DateTimeOffset expiresUtc = issuedUtc.Add(Options.ExpireTimeSpan); + + context.Properties.IssuedUtc = issuedUtc; + context.Properties.ExpiresUtc = expiresUtc; + + Options.Provider.ResponseSignIn(context); + + if (context.Properties.IsPersistent) + { + cookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime; + } + + var model = new AuthenticationTicket(context.Identity, context.Properties); + string cookieValue = Options.TicketDataFormat.Protect(model); + + Response.Cookies.Append( + Options.CookieName, + cookieValue, + cookieOptions); + } + else if (shouldSignout) + { + var context = new CookieResponseSignOutContext( + Context, + Options, + cookieOptions); + + Options.Provider.ResponseSignOut(context); + + Response.Cookies.Delete( + Options.CookieName, + cookieOptions); + } + else if (_shouldRenew) + { + AuthenticationTicket model = await AuthenticateAsync(); + + model.Properties.IssuedUtc = _renewIssuedUtc; + model.Properties.ExpiresUtc = _renewExpiresUtc; + + string cookieValue = Options.TicketDataFormat.Protect(model); + + if (model.Properties.IsPersistent) + { + cookieOptions.Expires = _renewExpiresUtc.ToUniversalTime().DateTime; + } + + Response.Cookies.Append( + Options.CookieName, + cookieValue, + cookieOptions); + } + + Response.Headers.Set( + HeaderNameCacheControl, + HeaderValueNoCache); + + Response.Headers.Set( + HeaderNamePragma, + HeaderValueNoCache); + + Response.Headers.Set( + HeaderNameExpires, + HeaderValueMinusOne); + + bool shouldLoginRedirect = shouldSignin && Options.LoginPath.HasValue && Request.Path == Options.LoginPath; + bool shouldLogoutRedirect = shouldSignout && Options.LogoutPath.HasValue && Request.Path == Options.LogoutPath; + + if ((shouldLoginRedirect || shouldLogoutRedirect) && Response.StatusCode == 200) + { + IReadableStringCollection query = Request.Query; + string redirectUri = query.Get(Options.ReturnUrlParameter); + if (!string.IsNullOrWhiteSpace(redirectUri) + && IsHostRelative(redirectUri)) + { + var redirectContext = new CookieApplyRedirectContext(Context, Options, redirectUri); + Options.Provider.ApplyRedirect(redirectContext); + } + } + } + } + + private static bool IsHostRelative(string path) + { + if (string.IsNullOrEmpty(path)) + { + return false; + } + if (path.Length == 1) + { + return path[0] == '/'; + } + return path[0] == '/' && path[1] != '/' && path[1] != '\\'; + } + + protected override void ApplyResponseChallenge() + { + if (Response.StatusCode != 401 || !Options.LoginPath.HasValue || ChallengeContext == null) + { + return; + } + + string currentUri = + Request.PathBase + + Request.Path + + Request.QueryString; + + string loginUri = + Request.Scheme + + "://" + + Request.Host + + Request.PathBase + + Options.LoginPath + + new QueryString(Options.ReturnUrlParameter, currentUri); + + var redirectContext = new CookieApplyRedirectContext(Context, Options, loginUri); + Options.Provider.ApplyRedirect(redirectContext); + } + } +} diff --git a/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationMiddleware.cs new file mode 100644 index 0000000000..b0ad64125f --- /dev/null +++ b/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationMiddleware.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Logging; +using Microsoft.AspNet.Security.DataHandler; +using Microsoft.AspNet.Security.DataProtection; +using Microsoft.AspNet.Security.Infrastructure; + +namespace Microsoft.AspNet.Security.Cookies +{ + internal class CookieAuthenticationMiddleware : AuthenticationMiddleware + { + private readonly ILogger _logger; + + public CookieAuthenticationMiddleware(RequestDelegate next, ILogger logger, CookieAuthenticationOptions options) + : base(next, options) + { + if (Options.Provider == null) + { + Options.Provider = new CookieAuthenticationProvider(); + } + if (String.IsNullOrEmpty(Options.CookieName)) + { + Options.CookieName = CookieAuthenticationDefaults.CookiePrefix + Options.AuthenticationType; + }/* + if (logger == null) + { + throw new ArgumentNullException("logger"); + }*/ + _logger = logger; + } + + protected override AuthenticationHandler CreateHandler() + { + return new CookieAuthenticationHandler(_logger); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationOptions.cs b/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationOptions.cs new file mode 100644 index 0000000000..389a611c33 --- /dev/null +++ b/src/Microsoft.AspNet.Security.Cookies/CookieAuthenticationOptions.cs @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Security.Infrastructure; + +namespace Microsoft.AspNet.Security.Cookies +{ + /// + /// Contains the options used by the CookiesAuthenticationMiddleware + /// + public class CookieAuthenticationOptions : AuthenticationOptions + { + private string _cookieName; + + /// + /// Create an instance of the options initialized with the default values + /// + public CookieAuthenticationOptions() + : base(CookieAuthenticationDefaults.AuthenticationType) + { + ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter; + CookiePath = "/"; + ExpireTimeSpan = TimeSpan.FromDays(14); + SlidingExpiration = true; + CookieHttpOnly = true; + CookieSecure = CookieSecureOption.SameAsRequest; + SystemClock = new SystemClock(); + Provider = new CookieAuthenticationProvider(); + } + + /// + /// Determines the cookie name used to persist the identity. The default value is ".AspNet.Cookies". + /// This value should be changed if you change the name of the AuthenticationType, especially if your + /// system uses the cookie authentication middleware multiple times. + /// + public string CookieName + { + get { return _cookieName; } + set + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + _cookieName = value; + } + } + + /// + /// Determines the domain used to create the cookie. Is not provided by default. + /// + public string CookieDomain { get; set; } + + /// + /// Determines the path used to create the cookie. The default value is "/" for highest browser compatability. + /// + public string CookiePath { get; set; } + + /// + /// Determines if the browser should allow the cookie to be accessed by client-side javascript. The + /// default is true, which means the cookie will only be passed to http requests and is not made available + /// to script on the page. + /// + public bool CookieHttpOnly { get; set; } + + /// + /// Determines if the cookie should only be transmitted on HTTPS request. The default is to limit the cookie + /// to HTTPS requests if the page which is doing the SignIn is also HTTPS. If you have an HTTPS sign in page + /// and portions of your site are HTTP you may need to change this value. + /// + public CookieSecureOption CookieSecure { get; set; } + + /// + /// Controls how much time the cookie will remain valid from the point it is created. The expiration + /// information is in the protected cookie ticket. Because of that an expired cookie will be ignored + /// even if it is passed to the server after the browser should have purged it + /// + public TimeSpan ExpireTimeSpan { get; set; } + + /// + /// The SlidingExpiration is set to true to instruct the middleware to re-issue a new cookie with a new + /// expiration time any time it processes a request which is more than halfway through the expiration window. + /// + public bool SlidingExpiration { get; set; } + + /// + /// The LoginPath property informs the middleware that it should change an outgoing 401 Unauthorized status + /// code into a 302 redirection onto the given login path. The current url which generated the 401 is added + /// to the LoginPath as a query string parameter named by the ReturnUrlParameter. Once a request to the + /// LoginPath grants a new SignIn identity, the ReturnUrlParameter value is used to redirect the browser back + /// to the url which caused the original unauthorized status code. + /// + /// If the LoginPath is null or empty, the middleware will not look for 401 Unauthorized status codes, and it will + /// not redirect automatically when a login occurs. + /// + [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Justification = "By design")] + public PathString LoginPath { get; set; } + + /// + /// If the LogoutPath is provided the middleware then a request to that path will redirect based on the ReturnUrlParameter. + /// + [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Logout", Justification = "By design")] + public PathString LogoutPath { get; set; } + + /// + /// The ReturnUrlParameter determines the name of the query string parameter which is appended by the middleware + /// when a 401 Unauthorized status code is changed to a 302 redirect onto the login path. This is also the query + /// string parameter looked for when a request arrives on the login path or logout path, in order to return to the + /// original url after the action is performed. + /// + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "ReturnUrl is the name of a querystring parameter")] + public string ReturnUrlParameter { get; set; } + + /// + /// The Provider may be assigned to an instance of an object created by the application at startup time. The middleware + /// calls methods on the provider which give the application control at certain points where processing is occuring. + /// If it is not provided a default instance is supplied which does nothing when the methods are called. + /// + public ICookieAuthenticationProvider Provider { get; set; } + + /// + /// The TicketDataFormat is used to protect and unprotect the identity and other properties which are stored in the + /// cookie value. If it is not provided a default data handler is created using the data protection service contained + /// in the IAppBuilder.Properties. The default data protection service is based on machine key when running on ASP.NET, + /// and on DPAPI when running in a different process. + /// + public ISecureDataFormat TicketDataFormat { get; set; } + + /// + /// The SystemClock provides access to the system's current time coordinates. If it is not provided a default instance is + /// used which calls DateTimeOffset.UtcNow. This is typically not replaced except for unit testing. + /// + public ISystemClock SystemClock { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Security.Cookies/CookieSecureOption.cs b/src/Microsoft.AspNet.Security.Cookies/CookieSecureOption.cs new file mode 100644 index 0000000000..19efae749b --- /dev/null +++ b/src/Microsoft.AspNet.Security.Cookies/CookieSecureOption.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security.Cookies +{ + /// + /// Determines how the identity cookie's security property is set. + /// + public enum CookieSecureOption + { + /// + /// If the URI that provides the cookie is HTTPS, then the cookie will only be returned to the server on + /// subsequent HTTPS requests. Otherwise if the URI that provides the cookie is HTTP, then the cookie will + /// be returned to the server on all HTTP and HTTPS requests. This is the default value because it ensures + /// HTTPS for all authenticated requests on deployed servers, and also supports HTTP for localhost development + /// and for servers that do not have HTTPS support. + /// + SameAsRequest, + + /// + /// CookieOptions.Secure is never marked true. Use this value when your login page is HTTPS, but other pages + /// on the site which are HTTP also require authentication information. This setting is not recommended because + /// the authentication information provided with an HTTP request may be observed and used by other computers + /// on your local network or wireless connection. + /// + Never, + + /// + /// CookieOptions.Secure is always marked true. Use this value when your login page and all subsequent pages + /// requiring the authenticated identity are HTTPS. Local development will also need to be done with HTTPS urls. + /// + Always, + } +} diff --git a/src/Microsoft.AspNet.Security.Cookies/Provider/CookieApplyRedirectContext.cs b/src/Microsoft.AspNet.Security.Cookies/Provider/CookieApplyRedirectContext.cs new file mode 100644 index 0000000000..69ccc0c386 --- /dev/null +++ b/src/Microsoft.AspNet.Security.Cookies/Provider/CookieApplyRedirectContext.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Security.Provider; + +namespace Microsoft.AspNet.Security.Cookies +{ + /// + /// Context passed when a Challenge, SignIn, or SignOut causes a redirect in the cookie middleware + /// + public class CookieApplyRedirectContext : BaseContext + { + /// + /// Creates a new context object. + /// + /// The OWIN request context + /// The cookie middleware options + /// The initial redirect URI + [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#", Justification = "Represents header value")] + public CookieApplyRedirectContext(HttpContext context, CookieAuthenticationOptions options, string redirectUri) + : base(context, options) + { + RedirectUri = redirectUri; + } + + /// + /// Gets or Sets the URI used for the redirect operation. + /// + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Represents header value")] + public string RedirectUri { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Security.Cookies/Provider/CookieAuthenticationProvider.cs b/src/Microsoft.AspNet.Security.Cookies/Provider/CookieAuthenticationProvider.cs new file mode 100644 index 0000000000..6b434c0b2b --- /dev/null +++ b/src/Microsoft.AspNet.Security.Cookies/Provider/CookieAuthenticationProvider.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Security.Cookies +{ + /// + /// This default implementation of the ICookieAuthenticationProvider may be used if the + /// application only needs to override a few of the interface methods. This may be used as a base class + /// or may be instantiated directly. + /// + public class CookieAuthenticationProvider : ICookieAuthenticationProvider + { + /// + /// Create a new instance of the default provider. + /// + public CookieAuthenticationProvider() + { + OnValidateIdentity = context => Task.FromResult(0); + OnResponseSignIn = context => { }; + OnResponseSignOut = context => { }; + OnApplyRedirect = DefaultBehavior.ApplyRedirect; + } + + /// + /// A delegate assigned to this property will be invoked when the related method is called + /// + public Func OnValidateIdentity { get; set; } + + /// + /// A delegate assigned to this property will be invoked when the related method is called + /// + public Action OnResponseSignIn { get; set; } + + /// + /// A delegate assigned to this property will be invoked when the related method is called + /// + public Action OnResponseSignOut { get; set; } + + /// + /// A delegate assigned to this property will be invoked when the related method is called + /// + public Action OnApplyRedirect { get; set; } + + /// + /// Implements the interface method by invoking the related delegate method + /// + /// + /// + public virtual Task ValidateIdentity(CookieValidateIdentityContext context) + { + return OnValidateIdentity.Invoke(context); + } + + /// + /// Implements the interface method by invoking the related delegate method + /// + /// + public virtual void ResponseSignIn(CookieResponseSignInContext context) + { + OnResponseSignIn.Invoke(context); + } + + /// + /// Implements the interface method by invoking the related delegate method + /// + /// + public virtual void ResponseSignOut(CookieResponseSignOutContext context) + { + OnResponseSignOut.Invoke(context); + } + + /// + /// Called when a Challenge, SignIn, or SignOut causes a redirect in the cookie middleware + /// + /// Contains information about the event + public void ApplyRedirect(CookieApplyRedirectContext context) + { + OnApplyRedirect.Invoke(context); + } + } +} diff --git a/src/Microsoft.AspNet.Security.Cookies/Provider/CookieResponseSignInContext.cs b/src/Microsoft.AspNet.Security.Cookies/Provider/CookieResponseSignInContext.cs new file mode 100644 index 0000000000..e3098e3590 --- /dev/null +++ b/src/Microsoft.AspNet.Security.Cookies/Provider/CookieResponseSignInContext.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Security.Claims; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Abstractions.Security; +using Microsoft.AspNet.Security.Provider; + +namespace Microsoft.AspNet.Security.Cookies +{ + /// + /// Context object passed to the ICookieAuthenticationProvider method ResponseSignIn. + /// + public class CookieResponseSignInContext : BaseContext + { + /// + /// Creates a new instance of the context object. + /// + /// The OWIN request context + /// The middleware options + /// Initializes AuthenticationType property + /// Initializes Identity property + /// Initializes Extra property + /// Initializes options for the authentication cookie. + public CookieResponseSignInContext( + HttpContext context, + CookieAuthenticationOptions options, + string authenticationType, + ClaimsIdentity identity, + AuthenticationProperties properties, + CookieOptions cookieOptions) + : base(context, options) + { + AuthenticationType = authenticationType; + Identity = identity; + Properties = properties; + CookieOptions = cookieOptions; + } + + /// + /// The name of the AuthenticationType creating a cookie + /// + public string AuthenticationType { get; private set; } + + /// + /// Contains the claims about to be converted into the outgoing cookie. + /// May be replaced or altered during the ResponseSignIn call. + /// + public ClaimsIdentity Identity { get; set; } + + /// + /// Contains the extra data about to be contained in the outgoing cookie. + /// May be replaced or altered during the ResponseSignIn call. + /// + public AuthenticationProperties Properties { get; set; } + + /// + /// The options for creating the outgoing cookie. + /// May be replace or altered during the ResponseSignIn call. + /// + public CookieOptions CookieOptions { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Security.Cookies/Provider/CookieResponseSignOutContext.cs b/src/Microsoft.AspNet.Security.Cookies/Provider/CookieResponseSignOutContext.cs new file mode 100644 index 0000000000..e17028982a --- /dev/null +++ b/src/Microsoft.AspNet.Security.Cookies/Provider/CookieResponseSignOutContext.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Security.Provider; + +namespace Microsoft.AspNet.Security.Cookies +{ + /// + /// Context object passed to the ICookieAuthenticationProvider method ResponseSignOut + /// + public class CookieResponseSignOutContext : BaseContext + { + /// + /// + /// + /// + /// + /// + public CookieResponseSignOutContext(HttpContext context, CookieAuthenticationOptions options, CookieOptions cookieOptions) + : base(context, options) + { + CookieOptions = cookieOptions; + } + + /// + /// The options for creating the outgoing cookie. + /// May be replace or altered during the ResponseSignIn call. + /// + public CookieOptions CookieOptions + { + get; + set; + } + } +} diff --git a/src/Microsoft.AspNet.Security.Cookies/Provider/CookieValidateIdentityContext.cs b/src/Microsoft.AspNet.Security.Cookies/Provider/CookieValidateIdentityContext.cs new file mode 100644 index 0000000000..4487582209 --- /dev/null +++ b/src/Microsoft.AspNet.Security.Cookies/Provider/CookieValidateIdentityContext.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Security.Claims; +using System.Security.Principal; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Abstractions.Security; +using Microsoft.AspNet.HttpFeature.Security; +using Microsoft.AspNet.Security.Infrastructure; +using Microsoft.AspNet.Security.Provider; + +namespace Microsoft.AspNet.Security.Cookies +{ + /// + /// Context object passed to the ICookieAuthenticationProvider method ValidateIdentity. + /// + public class CookieValidateIdentityContext : BaseContext + { + /// + /// Creates a new instance of the context object. + /// + /// + /// Contains the initial values for identity and extra data + /// + public CookieValidateIdentityContext(HttpContext context, AuthenticationTicket ticket, CookieAuthenticationOptions options) + : base(context, options) + { + if (ticket == null) + { + throw new ArgumentNullException("ticket"); + } + + Identity = ticket.Identity; + Properties = ticket.Properties; + } + + /// + /// Contains the claims identity arriving with the request. May be altered to change the + /// details of the authenticated user. + /// + public ClaimsIdentity Identity { get; private set; } + + /// + /// Contains the extra meta-data arriving with the request ticket. May be altered. + /// + public AuthenticationProperties Properties { get; private set; } + + /// + /// Called to replace the claims identity. The supplied identity will replace the value of the + /// Identity property, which determines the identity of the authenticated request. + /// + /// The identity used as the replacement + public void ReplaceIdentity(IIdentity identity) + { + Identity = new ClaimsIdentity(identity); + } + + /// + /// Called to reject the incoming identity. This may be done if the application has determined the + /// account is no longer active, and the request should be treated as if it was anonymous. + /// + public void RejectIdentity() + { + Identity = null; + } + } +} diff --git a/src/Microsoft.AspNet.Security.Cookies/Provider/DefaultBehavior.cs b/src/Microsoft.AspNet.Security.Cookies/Provider/DefaultBehavior.cs new file mode 100644 index 0000000000..d90acf62ff --- /dev/null +++ b/src/Microsoft.AspNet.Security.Cookies/Provider/DefaultBehavior.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Abstractions; +using Newtonsoft.Json; + +namespace Microsoft.AspNet.Security.Cookies +{ + internal static class DefaultBehavior + { + internal static readonly Action ApplyRedirect = context => + { + if (IsAjaxRequest(context.Request)) + { + string jsonResponse = JsonConvert.SerializeObject(new + { + status = context.Response.StatusCode, + headers = new + { + location = context.RedirectUri + } + }, Formatting.None); + + context.Response.StatusCode = 200; + context.Response.Headers.Append("X-Responded-JSON", jsonResponse); + } + else + { + context.Response.Redirect(context.RedirectUri); + } + }; + + private static bool IsAjaxRequest(HttpRequest request) + { + IReadableStringCollection query = request.Query; + if (query != null) + { + if (query["X-Requested-With"] == "XMLHttpRequest") + { + return true; + } + } + + IHeaderDictionary headers = request.Headers; + if (headers != null) + { + if (headers["X-Requested-With"] == "XMLHttpRequest") + { + return true; + } + } + return false; + } + } +} diff --git a/src/Microsoft.AspNet.Security.Cookies/Provider/ICookieAuthenticationProvider.cs b/src/Microsoft.AspNet.Security.Cookies/Provider/ICookieAuthenticationProvider.cs new file mode 100644 index 0000000000..f33a22259f --- /dev/null +++ b/src/Microsoft.AspNet.Security.Cookies/Provider/ICookieAuthenticationProvider.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Security.Cookies +{ + /// + /// Specifies callback methods which the invokes to enable developer control over the authentication process. /> + /// + public interface ICookieAuthenticationProvider + { + /// + /// 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. + /// + /// Contains information about the login session as well as the user . + /// A representing the completed operation. + Task ValidateIdentity(CookieValidateIdentityContext context); + + /// + /// Called when an endpoint has provided sign in information before it is converted into a cookie. By + /// implementing this method the claims and extra information that go into the ticket may be altered. + /// + /// Contains information about the login session as well as the user . + void ResponseSignIn(CookieResponseSignInContext context); + + /// + /// Called when a Challenge, SignIn, or SignOut causes a redirect in the cookie middleware + /// + /// Contains information about the event + void ApplyRedirect(CookieApplyRedirectContext context); + + /// + /// + /// + /// Contains information about the login session as well as information about the authentication cookie. + void ResponseSignOut(CookieResponseSignOutContext context); + } +} diff --git a/src/Microsoft.AspNet.Security.Cookies/project.json b/src/Microsoft.AspNet.Security.Cookies/project.json new file mode 100644 index 0000000000..ee461aabbd --- /dev/null +++ b/src/Microsoft.AspNet.Security.Cookies/project.json @@ -0,0 +1,42 @@ +{ + "version": "0.1-alpha-*", + "dependencies": { + "Newtonsoft.Json": "5.0.8", + "Microsoft.AspNet.Security": "0.1-alpha-*", + "Microsoft.AspNet.DependencyInjection": "0.1-alpha-*", + "Microsoft.AspNet.ConfigurationModel": "0.1-alpha-*", + "Microsoft.AspNet.PipelineCore": "0.1-alpha-*", + "Microsoft.AspNet.Abstractions": "0.1-alpha-*", + "Microsoft.AspNet.FeatureModel": "0.1-alpha-*", + "Microsoft.AspNet.HttpFeature": "0.1-alpha-*", + "Microsoft.AspNet.Logging": "0.1-alpha-*", + "Microsoft.AspNet.Security.DataProtection": "0.1-alpha-*" + }, + "configurations": { + "net45": {}, + "k10": { + "dependencies": { + "System.Collections": "4.0.0.0", + "System.Console": "4.0.0.0", + "System.ComponentModel": "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.IO.FileSystem": "4.0.0.0", + "System.IO.FileSystem.Primitives": "4.0.0.0", + "System.Linq": "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.10.0", + "System.Security.Claims": "0.1-alpha-*", + "System.Security.Principal" : "4.0.0.0", + "System.Threading": "4.0.0.0", + "System.Threading.Tasks": "4.0.0.0" + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/AuthenticationOptions.cs b/src/Microsoft.AspNet.Security/AuthenticationOptions.cs index 262bce7f3e..bfd63df993 100644 --- a/src/Microsoft.AspNet.Security/AuthenticationOptions.cs +++ b/src/Microsoft.AspNet.Security/AuthenticationOptions.cs @@ -1,5 +1,11 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; +using Microsoft.AspNet.Abstractions.Security; +using Microsoft.AspNet.HttpFeature.Security; +using Microsoft.AspNet.PipelineCore.Security; + namespace Microsoft.AspNet.Security { /// diff --git a/src/Microsoft.AspNet.Security/AuthenticationTicket.cs b/src/Microsoft.AspNet.Security/AuthenticationTicket.cs index ac2e31006f..2b910d9445 100644 --- a/src/Microsoft.AspNet.Security/AuthenticationTicket.cs +++ b/src/Microsoft.AspNet.Security/AuthenticationTicket.cs @@ -1,6 +1,12 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; using System.Security.Claims; +using Microsoft.AspNet.Abstractions.Security; +using Microsoft.AspNet.HttpFeature.Security; +using Microsoft.AspNet.PipelineCore.Security; +using Microsoft.AspNet.Security.Infrastructure; namespace Microsoft.AspNet.Security { diff --git a/src/Microsoft.AspNet.Security/CertificateSubjectKeyIdentifierValidator.cs b/src/Microsoft.AspNet.Security/CertificateSubjectKeyIdentifierValidator.cs index 8c1ade928f..a549c3740d 100644 --- a/src/Microsoft.AspNet.Security/CertificateSubjectKeyIdentifierValidator.cs +++ b/src/Microsoft.AspNet.Security/CertificateSubjectKeyIdentifierValidator.cs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. -/* TODO: +#if NET45 using System; using System.Collections.Generic; using System.Net.Security; @@ -85,4 +85,4 @@ namespace Microsoft.AspNet.Security } } } -*/ \ No newline at end of file +#endif \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/CertificateSubjectPublicKeyInfoValidator.cs b/src/Microsoft.AspNet.Security/CertificateSubjectPublicKeyInfoValidator.cs index 119b553f21..0896c72d97 100644 --- a/src/Microsoft.AspNet.Security/CertificateSubjectPublicKeyInfoValidator.cs +++ b/src/Microsoft.AspNet.Security/CertificateSubjectPublicKeyInfoValidator.cs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. -/* TODO: +#if NET45 using System; using System.Collections.Generic; using System.ComponentModel; @@ -132,4 +132,4 @@ namespace Microsoft.AspNet.Security } } } -*/ \ No newline at end of file +#endif \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/CertificateThumbprintValidator.cs b/src/Microsoft.AspNet.Security/CertificateThumbprintValidator.cs index cc1fddf397..464d579011 100644 --- a/src/Microsoft.AspNet.Security/CertificateThumbprintValidator.cs +++ b/src/Microsoft.AspNet.Security/CertificateThumbprintValidator.cs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. -/* TODO: +#if NET45 using System; using System.Collections.Generic; using System.Net.Security; @@ -78,4 +78,4 @@ namespace Microsoft.AspNet.Security } } } -*/ \ No newline at end of file +#endif \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/DataHandler/PropertiesDataFormat.cs b/src/Microsoft.AspNet.Security/DataHandler/PropertiesDataFormat.cs index 54d69c4f57..43aab1c990 100644 --- a/src/Microsoft.AspNet.Security/DataHandler/PropertiesDataFormat.cs +++ b/src/Microsoft.AspNet.Security/DataHandler/PropertiesDataFormat.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. +using Microsoft.AspNet.Abstractions.Security; using Microsoft.AspNet.Security.DataHandler.Encoder; using Microsoft.AspNet.Security.DataHandler.Serializer; using Microsoft.AspNet.Security.DataProtection; diff --git a/src/Microsoft.AspNet.Security/DataHandler/Serializer/DataSerializers.cs b/src/Microsoft.AspNet.Security/DataHandler/Serializer/DataSerializers.cs index 59585f6f9b..b46f5dcb98 100644 --- a/src/Microsoft.AspNet.Security/DataHandler/Serializer/DataSerializers.cs +++ b/src/Microsoft.AspNet.Security/DataHandler/Serializer/DataSerializers.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. +using Microsoft.AspNet.Abstractions.Security; + namespace Microsoft.AspNet.Security.DataHandler.Serializer { public static class DataSerializers diff --git a/src/Microsoft.AspNet.Security/DataHandler/Serializer/PropertiesSerializer.cs b/src/Microsoft.AspNet.Security/DataHandler/Serializer/PropertiesSerializer.cs index 2675932fc3..1ba7164059 100644 --- a/src/Microsoft.AspNet.Security/DataHandler/Serializer/PropertiesSerializer.cs +++ b/src/Microsoft.AspNet.Security/DataHandler/Serializer/PropertiesSerializer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using Microsoft.AspNet.Abstractions.Security; namespace Microsoft.AspNet.Security.DataHandler.Serializer { diff --git a/src/Microsoft.AspNet.Security/DataHandler/Serializer/TicketSerializer.cs b/src/Microsoft.AspNet.Security/DataHandler/Serializer/TicketSerializer.cs index 4ca3c0a6af..de3abbd92e 100644 --- a/src/Microsoft.AspNet.Security/DataHandler/Serializer/TicketSerializer.cs +++ b/src/Microsoft.AspNet.Security/DataHandler/Serializer/TicketSerializer.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; @@ -98,7 +99,7 @@ namespace Microsoft.AspNet.Security.DataHandler.Serializer claims[index] = new Claim(type, value, valueType, issuer, originalIssuer); } var identity = new ClaimsIdentity(claims, authenticationType, nameClaimType, roleClaimType); - AuthenticationProperties properties = PropertiesSerializer.Read(reader); + var properties = PropertiesSerializer.Read(reader); return new AuthenticationTicket(identity, properties); } diff --git a/src/Microsoft.AspNet.Security/ICertificateValidator.cs b/src/Microsoft.AspNet.Security/ICertificateValidator.cs index 8d062aecfe..3b84fb4850 100644 --- a/src/Microsoft.AspNet.Security/ICertificateValidator.cs +++ b/src/Microsoft.AspNet.Security/ICertificateValidator.cs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. -/* TODO: +#if NET45 using System; using System.Net.Security; using System.Security.Cryptography.X509Certificates; @@ -25,4 +25,4 @@ namespace Microsoft.AspNet.Security bool Validate(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors); } } -*/ \ No newline at end of file +#endif \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationHandler.cs b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationHandler.cs index 39d6596d3c..986826f792 100644 --- a/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationHandler.cs @@ -1,25 +1,29 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Security.Claims; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Abstractions.Security; +using Microsoft.AspNet.HttpFeature.Security; using Microsoft.AspNet.Logging; using Microsoft.AspNet.Security.DataHandler.Encoder; -using Microsoft.AspNet.Abstractions; namespace Microsoft.AspNet.Security.Infrastructure { /// /// Base class for the per-request work performed by most authentication middleware. /// - public abstract class AuthenticationHandler + public abstract class AuthenticationHandler : IAuthenticationHandler { +#if NET45 private static readonly RNGCryptoServiceProvider Random = new RNGCryptoServiceProvider(); - - private object _registration; - +#endif private Task _authenticate; private bool _authenticateInitialized; private object _authenticateSyncLock; @@ -30,6 +34,10 @@ namespace Microsoft.AspNet.Security.Infrastructure private AuthenticationOptions _baseOptions; + protected IChallengeContext ChallengeContext { get; set; } + protected SignInIdentityContext SignInIdentityContext { get; set; } + protected ISignOutContext SignOutContext { get; set; } + protected HttpContext Context { get; private set; } protected HttpRequest Request @@ -43,21 +51,21 @@ namespace Microsoft.AspNet.Security.Infrastructure } protected PathString RequestPathBase { get; private set; } - protected SecurityHelper Helper { get; private set; } internal AuthenticationOptions BaseOptions { get { return _baseOptions; } } + public IAuthenticationHandler PriorHandler { get; set; } + protected async Task BaseInitializeAsync(AuthenticationOptions options, HttpContext context) { _baseOptions = options; Context = context; - Helper = new SecurityHelper(context); RequestPathBase = Request.PathBase; - _registration = Request.RegisterAuthenticationHandler(this); + RegisterAuthenticationHandler(); Response.OnSendingHeaders(OnSendingHeaderCallback, this); @@ -68,7 +76,7 @@ namespace Microsoft.AspNet.Security.Infrastructure AuthenticationTicket ticket = await AuthenticateAsync(); if (ticket != null && ticket.Identity != null) { - Helper.AddUserIdentity(ticket.Identity); + Context.AddUserIdentity(ticket.Identity); } } } @@ -76,12 +84,12 @@ namespace Microsoft.AspNet.Security.Infrastructure private static void OnSendingHeaderCallback(object state) { AuthenticationHandler handler = (AuthenticationHandler)state; - handler.ApplyResponseAsync().Wait(); + handler.ApplyResponse(); } protected virtual Task InitializeCoreAsync() { - return Task.FromResult(null); + return Task.FromResult(0); } /// @@ -92,12 +100,12 @@ namespace Microsoft.AspNet.Security.Infrastructure { await ApplyResponseAsync(); await TeardownCoreAsync(); - Request.UnregisterAuthenticationHandler(_registration); + UnregisterAuthenticationHandler(); } protected virtual Task TeardownCoreAsync() { - return Task.FromResult(null); + return Task.FromResult(0); } /// @@ -113,6 +121,63 @@ namespace Microsoft.AspNet.Security.Infrastructure return Task.FromResult(false); } + public virtual void GetDescriptions(DescriptionDelegate callback, object state) + { + callback(BaseOptions.Description.Dictionary, state); + if (PriorHandler != null) + { + PriorHandler.GetDescriptions(callback, state); + } + } + + public virtual void Authenticate(IAuthenticateContext context) + { + if (context.AuthenticationTypes.Contains(BaseOptions.AuthenticationType, StringComparer.Ordinal)) + { + AuthenticationTicket ticket = Authenticate(); + if (ticket != null && ticket.Identity != null) + { + context.Authenticated(ticket.Identity, ticket.Properties.Dictionary, BaseOptions.Description.Dictionary); + } + } + + if (PriorHandler != null) + { + PriorHandler.Authenticate(context); + } + } + + public AuthenticationTicket Authenticate() + { + return LazyInitializer.EnsureInitialized( + ref _authenticate, + ref _authenticateInitialized, + ref _authenticateSyncLock, + () => + { + return Task.FromResult(AuthenticateCore()); + }).Result; + } + + protected abstract AuthenticationTicket AuthenticateCore(); + + public virtual async Task AuthenticateAsync(IAuthenticateContext context) + { + if (context.AuthenticationTypes.Contains(BaseOptions.AuthenticationType, StringComparer.Ordinal)) + { + AuthenticationTicket ticket = await AuthenticateAsync(); + if (ticket != null && ticket.Identity != null) + { + context.Authenticated(ticket.Identity, ticket.Properties.Dictionary, BaseOptions.Description.Dictionary); + } + } + + if (PriorHandler != null) + { + await PriorHandler.AuthenticateAsync(context); + } + } + /// /// Causes the authentication logic in AuthenticateCore to be performed for the current request /// at most once and returns the results. Calling Authenticate more than once will always return @@ -135,7 +200,29 @@ namespace Microsoft.AspNet.Security.Infrastructure /// once per request. Do not call directly, call the wrapping Authenticate method instead. /// /// The ticket data provided by the authentication logic - protected abstract Task AuthenticateCoreAsync(); + protected virtual Task AuthenticateCoreAsync() + { + return Task.FromResult(AuthenticateCore()); + } + + private void ApplyResponse() + { + LazyInitializer.EnsureInitialized( + ref _applyResponse, + ref _applyResponseInitialized, + ref _applyResponseSyncLock, + () => + { + ApplyResponseCore(); + return Task.FromResult(0); + }).Wait(); // Block if the async version is in progress. + } + + protected virtual void ApplyResponseCore() + { + ApplyResponseGrant(); + ApplyResponseChallenge(); + } /// /// Causes the ApplyResponseCore to be invoked at most once per request. This method will be @@ -163,6 +250,8 @@ namespace Microsoft.AspNet.Security.Infrastructure await ApplyResponseChallengeAsync(); } + protected abstract void ApplyResponseGrant(); + /// /// Override this method to dela with sign-in/sign-out concerns, if an authentication scheme in question /// deals with grant/revoke as part of it's request flow. (like setting/deleting cookies) @@ -170,18 +259,67 @@ namespace Microsoft.AspNet.Security.Infrastructure /// protected virtual Task ApplyResponseGrantAsync() { - return Task.FromResult(null); + ApplyResponseGrant(); + return Task.FromResult(0); } + public virtual void SignIn(ISignInContext context) + { + ClaimsIdentity identity; + if (SecurityHelper.LookupSignIn(context.User, BaseOptions.AuthenticationType, out identity)) + { + SignInIdentityContext = new SignInIdentityContext(identity, new AuthenticationProperties(context.Properties)); + SignOutContext = null; + context.Ack(BaseOptions.AuthenticationType, BaseOptions.Description.Dictionary); + } + + if (PriorHandler != null) + { + PriorHandler.SignIn(context); + } + } + + public virtual void SignOut(ISignOutContext context) + { + if (SecurityHelper.LookupSignOut(context.AuthenticationTypes, BaseOptions.AuthenticationType, BaseOptions.AuthenticationMode)) + { + SignInIdentityContext = null; + SignOutContext = context; + context.Ack(BaseOptions.AuthenticationType, BaseOptions.Description.Dictionary); + } + + if (PriorHandler != null) + { + PriorHandler.SignOut(context); + } + } + + public virtual void Challenge(IChallengeContext context) + { + if (SecurityHelper.LookupChallenge(context.AuthenticationTypes, BaseOptions.AuthenticationType, BaseOptions.AuthenticationMode)) + { + ChallengeContext = context; + context.Ack(BaseOptions.AuthenticationType, BaseOptions.Description.Dictionary); + } + + if (PriorHandler != null) + { + PriorHandler.Challenge(context); + } + } + + protected abstract void ApplyResponseChallenge(); + /// - /// Override this method to dela with 401 challenge concerns, if an authentication scheme in question + /// Override this method to deal with 401 challenge concerns, if an authentication scheme in question /// deals an authentication interaction as part of it's request flow. (like adding a response header, or /// changing the 401 result to 302 of a login page or external sign-in location.) /// /// protected virtual Task ApplyResponseChallengeAsync() { - return Task.FromResult(null); + ApplyResponseChallenge(); + return Task.FromResult(0); } protected void GenerateCorrelationId(AuthenticationProperties properties) @@ -194,7 +332,11 @@ namespace Microsoft.AspNet.Security.Infrastructure string correlationKey = Constants.CorrelationPrefix + BaseOptions.AuthenticationType; var nonceBytes = new byte[32]; +#if NET45 Random.GetBytes(nonceBytes); +#else + Microsoft.AspNet.Security.DataProtection.CryptRand.FillBuffer(new ArraySegment(nonceBytes)); +#endif string correlationId = TextEncodings.Base64Url.Encode(nonceBytes); var cookieOptions = new CookieOptions @@ -248,5 +390,18 @@ namespace Microsoft.AspNet.Security.Infrastructure return true; } + + private void RegisterAuthenticationHandler() + { + var auth = Context.GetAuthentication(); + PriorHandler = auth.Handler; + auth.Handler = this; + } + + private void UnregisterAuthenticationHandler() + { + var auth = Context.GetAuthentication(); + auth.Handler = PriorHandler; + } } } diff --git a/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationMiddleware.cs b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationMiddleware.cs index a77eb080e6..c50afdc3c7 100644 --- a/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationMiddleware.cs +++ b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationMiddleware.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Security.Infrastructure public TOptions Options { get; set; } - public override async Task Invoke(HttpContext context) + public async Task Invoke(HttpContext context) { AuthenticationHandler handler = CreateHandler(); await handler.Initialize(Options, context); diff --git a/src/Microsoft.AspNet.Security/Infrastructure/HttpContextExtensions.cs b/src/Microsoft.AspNet.Security/Infrastructure/HttpContextExtensions.cs new file mode 100644 index 0000000000..a46ef4079c --- /dev/null +++ b/src/Microsoft.AspNet.Security/Infrastructure/HttpContextExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.HttpFeature.Security; +using Microsoft.AspNet.PipelineCore.Security; + +namespace Microsoft.AspNet.Security.Infrastructure +{ + internal static class HttpContextExtensions + { + internal static IHttpAuthentication GetAuthentication(this HttpContext context) + { + var auth = context.GetFeature(); + if (auth == null) + { + auth = new DefaultHttpAuthentication(); + context.SetFeature(auth); + } + return auth; + } + } +} diff --git a/src/Microsoft.AspNet.Security/Infrastructure/ISystemClock.cs b/src/Microsoft.AspNet.Security/Infrastructure/ISystemClock.cs new file mode 100644 index 0000000000..cca20fbcb1 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Infrastructure/ISystemClock.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.Security.Infrastructure +{ + /// + /// Abstracts the system clock to facilitate testing. + /// + public interface ISystemClock + { + /// + /// Retrieves the current system time in UTC. + /// + DateTimeOffset UtcNow { get; } + } +} diff --git a/src/Microsoft.AspNet.Security/Infrastructure/OwinRequestExtensions.cs b/src/Microsoft.AspNet.Security/Infrastructure/OwinRequestExtensions.cs deleted file mode 100644 index 6fafca551d..0000000000 --- a/src/Microsoft.AspNet.Security/Infrastructure/OwinRequestExtensions.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Principal; -using System.Threading.Tasks; -using Microsoft.AspNet.Abstractions; - -namespace Microsoft.AspNet.Security.Infrastructure -{ - // TODO: comment function documentations - using AuthenticateCallback = Action, IDictionary, object>; - using AuthenticateDelegate = Func, IDictionary, object>, object, Task>; - - internal static class OwinRequestExtensions - { - public static object RegisterAuthenticationHandler(this HttpRequest request, AuthenticationHandler handler) - { - var chained = request.Get(Constants.SecurityAuthenticate); - var hook = new Hook(handler, chained); - request.Set(Constants.SecurityAuthenticate, hook.AuthenticateAsync); - return hook; - } - - public static void UnregisterAuthenticationHandler(this HttpRequest request, object registration) - { - var hook = registration as Hook; - if (hook == null) - { - throw new InvalidOperationException(Resources.Exception_UnhookAuthenticationStateType); - } - request.Set(Constants.SecurityAuthenticate, hook.Chained); - } - - private class Hook - { - private readonly AuthenticationHandler _handler; - - public Hook(AuthenticationHandler handler, AuthenticateDelegate chained) - { - _handler = handler; - Chained = chained; - } - - public AuthenticateDelegate Chained { get; private set; } - - public async Task AuthenticateAsync( - string[] authenticationTypes, - AuthenticateCallback callback, - object state) - { - if (authenticationTypes == null) - { - callback(null, null, _handler.BaseOptions.Description.Properties, state); - } - else if (authenticationTypes.Contains(_handler.BaseOptions.AuthenticationType, StringComparer.Ordinal)) - { - AuthenticationTicket ticket = await _handler.AuthenticateAsync(); - if (ticket != null && ticket.Identity != null) - { - callback(ticket.Identity, ticket.Properties.Dictionary, _handler.BaseOptions.Description.Properties, state); - } - } - if (Chained != null) - { - await Chained(authenticationTypes, callback, state); - } - } - } - } -} diff --git a/src/Microsoft.AspNet.Security/Infrastructure/SecurityHelper.cs b/src/Microsoft.AspNet.Security/Infrastructure/SecurityHelper.cs index a24b7245fd..7275da262c 100644 --- a/src/Microsoft.AspNet.Security/Infrastructure/SecurityHelper.cs +++ b/src/Microsoft.AspNet.Security/Infrastructure/SecurityHelper.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. 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.Security.Principal; using Microsoft.AspNet.Abstractions; @@ -10,29 +12,13 @@ namespace Microsoft.AspNet.Security.Infrastructure /// /// Helper code used when implementing authentication middleware /// - public struct SecurityHelper + public static class SecurityHelper { - private readonly HttpContext _context; - /// - /// Helper code used when implementing authentication middleware - /// - /// - public SecurityHelper(HttpContext context) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - _context = context; - } - - /// - /// Add an additional ClaimsIdentity to the ClaimsPrincipal in the "server.User" environment key + /// Add an additional ClaimsIdentity to the ClaimsPrincipal /// /// - public void AddUserIdentity(IIdentity identity) + public static void AddUserIdentity(this HttpContext context, IIdentity identity) { if (identity == null) { @@ -40,88 +26,46 @@ namespace Microsoft.AspNet.Security.Infrastructure } var newClaimsPrincipal = new ClaimsPrincipal(identity); - IPrincipal existingPrincipal = _context.Request.User; + ClaimsPrincipal existingPrincipal = context.User; if (existingPrincipal != null) { - var existingClaimsPrincipal = existingPrincipal as ClaimsPrincipal; - if (existingClaimsPrincipal == null) + foreach (var existingClaimsIdentity in existingPrincipal.Identities) { - IIdentity existingIdentity = existingPrincipal.Identity; - if (existingIdentity.IsAuthenticated) + if (existingClaimsIdentity.IsAuthenticated) { - newClaimsPrincipal.AddIdentity(existingIdentity as ClaimsIdentity ?? new ClaimsIdentity(existingIdentity)); - } - } - else - { - foreach (var existingClaimsIdentity in existingClaimsPrincipal.Identities) - { - if (existingClaimsIdentity.IsAuthenticated) - { - newClaimsPrincipal.AddIdentity(existingClaimsIdentity); - } + newClaimsPrincipal.AddIdentity(existingClaimsIdentity); } } } - _context.Request.User = newClaimsPrincipal; + context.User = newClaimsPrincipal; } - /// - /// Find response challenge details for a specific authentication middleware - /// - /// The authentication type to look for - /// The authentication mode the middleware is running under - /// The information instructing the middleware how it should behave - public AuthenticationResponseChallenge LookupChallenge(string authenticationType, AuthenticationMode authenticationMode) + public static bool LookupChallenge(IList authenticationTypes, string authenticationType, AuthenticationMode authenticationMode) { - if (authenticationType == null) + bool challengeHasAuthenticationTypes = authenticationTypes != null && authenticationTypes.Any(); + if (!challengeHasAuthenticationTypes) { - throw new ArgumentNullException("authenticationType"); + return authenticationMode == AuthenticationMode.Active; } - - AuthenticationResponseChallenge challenge = _context.Authentication.AuthenticationResponseChallenge; - bool challengeHasAuthenticationTypes = challenge != null && challenge.AuthenticationTypes != null && challenge.AuthenticationTypes.Length != 0; - if (challengeHasAuthenticationTypes == false) - { - return authenticationMode == AuthenticationMode.Active ? (challenge ?? new AuthenticationResponseChallenge(null, null)) : null; - } - foreach (var challengeType in challenge.AuthenticationTypes) - { - if (string.Equals(challengeType, authenticationType, StringComparison.Ordinal)) - { - return challenge; - } - } - return null; + return authenticationTypes.Contains(authenticationType, StringComparer.Ordinal); } /// /// Find response sign-in details for a specific authentication middleware /// /// The authentication type to look for - /// The information instructing the middleware how it should behave - public AuthenticationResponseGrant LookupSignIn(string authenticationType) + public static bool LookupSignIn(ClaimsPrincipal user, string authenticationType, out ClaimsIdentity identity) { - if (authenticationType == null) - { - throw new ArgumentNullException("authenticationType"); - } - - AuthenticationResponseGrant grant = _context.Authentication.AuthenticationResponseGrant; - if (grant == null) - { - return null; - } - - foreach (var claimsIdentity in grant.Principal.Identities) + identity = null; + foreach (var claimsIdentity in user.Identities) { if (string.Equals(authenticationType, claimsIdentity.AuthenticationType, StringComparison.Ordinal)) { - return new AuthenticationResponseGrant(claimsIdentity, grant.Properties ?? new AuthenticationProperties()); + identity = claimsIdentity; + return true; } } - - return null; + return false; } /// @@ -129,60 +73,14 @@ namespace Microsoft.AspNet.Security.Infrastructure /// /// The authentication type to look for /// The authentication mode the middleware is running under - /// The information instructing the middleware how it should behave - public AuthenticationResponseRevoke LookupSignOut(string authenticationType, AuthenticationMode authenticationMode) + public static bool LookupSignOut(IList authenticationTypes, string authenticationType, AuthenticationMode authenticationMode) { - if (authenticationType == null) + bool singOutHasAuthenticationTypes = authenticationTypes != null && authenticationTypes.Any(); + if (!singOutHasAuthenticationTypes) { - throw new ArgumentNullException("authenticationType"); + return authenticationMode == AuthenticationMode.Active; } - - AuthenticationResponseRevoke revoke = _context.Authentication.AuthenticationResponseRevoke; - if (revoke == null) - { - return null; - } - if (revoke.AuthenticationTypes == null || revoke.AuthenticationTypes.Length == 0) - { - return authenticationMode == AuthenticationMode.Active ? revoke : null; - } - for (int index = 0; index != revoke.AuthenticationTypes.Length; ++index) - { - if (String.Equals(authenticationType, revoke.AuthenticationTypes[index], StringComparison.Ordinal)) - { - return revoke; - } - } - return null; + return authenticationTypes.Contains(authenticationType, StringComparer.Ordinal); } - - #region Value-type equality - - public bool Equals(SecurityHelper other) - { - return Equals(_context, other._context); - } - - public override bool Equals(object obj) - { - return obj is SecurityHelper && Equals((SecurityHelper)obj); - } - - public override int GetHashCode() - { - return (_context != null ? _context.GetHashCode() : 0); - } - - public static bool operator ==(SecurityHelper left, SecurityHelper right) - { - return left.Equals(right); - } - - public static bool operator !=(SecurityHelper left, SecurityHelper right) - { - return !left.Equals(right); - } - - #endregion } } diff --git a/src/Microsoft.AspNet.Security/Infrastructure/SignInIdentityContext.cs b/src/Microsoft.AspNet.Security/Infrastructure/SignInIdentityContext.cs new file mode 100644 index 0000000000..087de642c6 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Infrastructure/SignInIdentityContext.cs @@ -0,0 +1,17 @@ +using System.Security.Claims; +using Microsoft.AspNet.Abstractions.Security; + +namespace Microsoft.AspNet.Security.Infrastructure +{ + public class SignInIdentityContext + { + public SignInIdentityContext(ClaimsIdentity identity, AuthenticationProperties properties) + { + Identity = identity; + Properties = properties; + } + + public ClaimsIdentity Identity { get; private set; } + public AuthenticationProperties Properties { get; private set; } + } +} diff --git a/src/Microsoft.AspNet.Security/Infrastructure/SystemClock.cs b/src/Microsoft.AspNet.Security/Infrastructure/SystemClock.cs new file mode 100644 index 0000000000..2dcbca0fe9 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Infrastructure/SystemClock.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 System; + +namespace Microsoft.AspNet.Security.Infrastructure +{ + /// + /// Provides access to the normal system clock. + /// + public class SystemClock : ISystemClock + { + /// + /// Retrieves the current system time in UTC. + /// + public DateTimeOffset UtcNow + { + get + { + // the clock measures whole seconds only, to have integral expires_in results, and + // because milliseconds do not round-trip serialization formats + DateTimeOffset utcNow = DateTimeOffset.UtcNow; + return utcNow.AddMilliseconds(-utcNow.Millisecond); + } + } + } +} diff --git a/src/Microsoft.AspNet.Security/Notifications/AuthenticationFailedNotification.cs b/src/Microsoft.AspNet.Security/Notifications/AuthenticationFailedNotification.cs index 02bb1dbecc..77c193bb47 100644 --- a/src/Microsoft.AspNet.Security/Notifications/AuthenticationFailedNotification.cs +++ b/src/Microsoft.AspNet.Security/Notifications/AuthenticationFailedNotification.cs @@ -13,6 +13,5 @@ namespace Microsoft.AspNet.Security.Notifications public bool Cancel { get; set; } public Exception Exception { get; set; } public TMessage ProtocolMessage { get; set; } - public int StatusCode { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/Notifications/MessageReceivedNotification.cs b/src/Microsoft.AspNet.Security/Notifications/MessageReceivedNotification.cs index 6bf2fed5ff..af81a2dbff 100644 --- a/src/Microsoft.AspNet.Security/Notifications/MessageReceivedNotification.cs +++ b/src/Microsoft.AspNet.Security/Notifications/MessageReceivedNotification.cs @@ -10,6 +10,5 @@ namespace Microsoft.AspNet.Security.Notifications public bool Cancel { get; set; } public TMessage ProtocolMessage { get; set; } - public int StatusCode { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/Notifications/RedirectFromIdentityProviderNotification.cs b/src/Microsoft.AspNet.Security/Notifications/RedirectFromIdentityProviderNotification.cs index d31689aee1..a8a1dcfd57 100644 --- a/src/Microsoft.AspNet.Security/Notifications/RedirectFromIdentityProviderNotification.cs +++ b/src/Microsoft.AspNet.Security/Notifications/RedirectFromIdentityProviderNotification.cs @@ -10,8 +10,6 @@ namespace Microsoft.AspNet.Security.Notifications public bool Cancel { get; set; } - public int StatusCode { get; set; } - public bool IsRequestCompleted { get; set; } } } diff --git a/src/Microsoft.AspNet.Security/Notifications/RedirectToIdentityProviderNotification.cs b/src/Microsoft.AspNet.Security/Notifications/RedirectToIdentityProviderNotification.cs index 18d466d563..91dfb87938 100644 --- a/src/Microsoft.AspNet.Security/Notifications/RedirectToIdentityProviderNotification.cs +++ b/src/Microsoft.AspNet.Security/Notifications/RedirectToIdentityProviderNotification.cs @@ -10,6 +10,5 @@ namespace Microsoft.AspNet.Security.Notifications public bool Cancel { get; set; } public TMessage ProtocolMessage { get; set; } - public int StatusCode { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/Notifications/SecurityTokenReceivedNotification.cs b/src/Microsoft.AspNet.Security/Notifications/SecurityTokenReceivedNotification.cs index ecd03f77ac..2ac5c33684 100644 --- a/src/Microsoft.AspNet.Security/Notifications/SecurityTokenReceivedNotification.cs +++ b/src/Microsoft.AspNet.Security/Notifications/SecurityTokenReceivedNotification.cs @@ -10,6 +10,5 @@ namespace Microsoft.AspNet.Security.Notifications public bool Cancel { get; set; } public string SecurityToken { get; set; } - public int StatusCode { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/Notifications/SecurityTokenValidatedNotification.cs b/src/Microsoft.AspNet.Security/Notifications/SecurityTokenValidatedNotification.cs index 55f56cad1f..963266e935 100644 --- a/src/Microsoft.AspNet.Security/Notifications/SecurityTokenValidatedNotification.cs +++ b/src/Microsoft.AspNet.Security/Notifications/SecurityTokenValidatedNotification.cs @@ -10,6 +10,5 @@ namespace Microsoft.AspNet.Security.Notifications public AuthenticationTicket AuthenticationTicket { get; set; } public bool Cancel { get; set; } - public int StatusCode { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/Provider/ReturnEndpointContext.cs b/src/Microsoft.AspNet.Security/Provider/ReturnEndpointContext.cs index 6d3e16f671..13e712eb9f 100644 --- a/src/Microsoft.AspNet.Security/Provider/ReturnEndpointContext.cs +++ b/src/Microsoft.AspNet.Security/Provider/ReturnEndpointContext.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Security.Claims; using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Abstractions.Security; namespace Microsoft.AspNet.Security.Provider { diff --git a/src/Microsoft.AspNet.Security/Resources.Designer.cs b/src/Microsoft.AspNet.Security/Resources.Designer.cs index f7bfbfaf71..26e74bb6c0 100644 --- a/src/Microsoft.AspNet.Security/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Security/Resources.Designer.cs @@ -39,7 +39,7 @@ namespace Microsoft.AspNet.Security { 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.Resources", typeof(Resources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.Security.Resources", System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(Resources)).Assembly); resourceMan = temp; } return resourceMan; diff --git a/src/Microsoft.AspNet.Security/project.json b/src/Microsoft.AspNet.Security/project.json index 5106bf0151..dab15d9534 100644 --- a/src/Microsoft.AspNet.Security/project.json +++ b/src/Microsoft.AspNet.Security/project.json @@ -8,7 +8,7 @@ "Microsoft.AspNet.FeatureModel": "0.1-alpha-*", "Microsoft.AspNet.HttpFeature": "0.1-alpha-*", "Microsoft.AspNet.Logging": "0.1-alpha-*", - "Microsoft.AspNet.Security.DataProtection": "0.1-alpha-*" + "Microsoft.AspNet.Security.DataProtection": "0.1-alpha-*" }, "configurations": { "net45": {},