// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Security.Cryptography; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Features; using Microsoft.Framework.Internal; using Microsoft.Framework.Logging; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Session { /// /// Enables the session state for the application. /// public class SessionMiddleware { private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create(); private const int SessionKeyLength = 36; // "382c74c3-721d-4f34-80e5-57657b6cbc27" private static readonly Func ReturnTrue = () => true; private readonly RequestDelegate _next; private readonly SessionOptions _options; private readonly ILogger _logger; private readonly ISessionStore _sessionStore; /// /// Creates a new . /// /// The representing the next middleware in the pipeline. /// The representing the factory that used to create logger instances. /// The representing the session store. /// The session configuration options. public SessionMiddleware( [NotNull] RequestDelegate next, [NotNull] ILoggerFactory loggerFactory, [NotNull] ISessionStore sessionStore, [NotNull] IOptions options) { _next = next; _logger = loggerFactory.CreateLogger(); _options = options.Options; _sessionStore = sessionStore; _sessionStore.Connect(); } /// /// Invokes the logic of the middleware. /// /// The . /// A that completes when the middleware has completed processing. public async Task Invoke(HttpContext context) { var isNewSessionKey = false; Func tryEstablishSession = ReturnTrue; string sessionKey = context.Request.Cookies[_options.CookieName]; if (string.IsNullOrWhiteSpace(sessionKey) || sessionKey.Length != SessionKeyLength) { // No valid cookie, new session. var guidBytes = new byte[16]; CryptoRandom.GetBytes(guidBytes); sessionKey = new Guid(guidBytes).ToString(); var establisher = new SessionEstablisher(context, sessionKey, _options); tryEstablishSession = establisher.TryEstablishSession; isNewSessionKey = true; } var feature = new SessionFeature(); feature.Session = _sessionStore.Create(sessionKey, _options.IdleTimeout, tryEstablishSession, isNewSessionKey); context.SetFeature(feature); try { await _next(context); } finally { context.SetFeature(null); if (feature.Session != null) { try { await feature.Session.CommitAsync(); } catch (Exception ex) { _logger.LogError("Error closing the session.", ex); } } } } private class SessionEstablisher { private readonly HttpContext _context; private readonly string _sessionKey; private readonly SessionOptions _options; private bool _shouldEstablishSession; public SessionEstablisher(HttpContext context, string sessionKey, SessionOptions options) { _context = context; _sessionKey = sessionKey; _options = options; context.Response.OnStarting(OnStartingCallback, state: this); } private static Task OnStartingCallback(object state) { var establisher = (SessionEstablisher)state; if (establisher._shouldEstablishSession) { establisher.SetCookie(); } return Task.FromResult(0); } private void SetCookie() { var cookieOptions = new CookieOptions { Domain = _options.CookieDomain, HttpOnly = _options.CookieHttpOnly, Path = _options.CookiePath ?? SessionDefaults.CookiePath, }; _context.Response.Cookies.Append(_options.CookieName, _sessionKey, cookieOptions); _context.Response.Headers["Cache-Control"] = "no-cache"; _context.Response.Headers["Pragma"] = "no-cache"; _context.Response.Headers["Expires"] = "-1"; } // Returns true if the session has already been established, or if it still can be because the response has not been sent. internal bool TryEstablishSession() { return (_shouldEstablishSession |= !_context.Response.HasStarted); } } } }