/// <summary> /// Gets the authenticated user GUID from the incoming request cookie if it exists /// and is valid. /// </summary> /// <param name="context">Current context</param> /// <param name="configuration">Current configuration</param> /// <returns>Returns user guid, or Guid.Empty if not present or invalid</returns> private static Guid GetAuthenticatedUserFromCookie(NancyContext context, CustomAuthenticationConfiguration configuration) { if (!context.Request.Cookies.ContainsKey(formsAuthenticationCookieName)) { return(Guid.Empty); } var cookieValueEncrypted = context.Request.Cookies[formsAuthenticationCookieName]; if (string.IsNullOrEmpty(cookieValueEncrypted)) { return(Guid.Empty); } var cookieValue = DecryptAndValidateAuthenticationCookie(cookieValueEncrypted, configuration); Guid returnGuid; if (string.IsNullOrEmpty(cookieValue) || !Guid.TryParse(cookieValue, out returnGuid)) { return(Guid.Empty); } return(returnGuid); }
/// <summary> /// Encrypt and sign the cookie contents /// </summary> /// <param name="cookieValue">Plain text cookie value</param> /// <param name="configuration">Current configuration</param> /// <returns>Encrypted and signed string</returns> private static string EncryptAndSignCookie(string cookieValue, CustomAuthenticationConfiguration configuration) { var encryptedCookie = configuration.CryptographyConfiguration.EncryptionProvider.Encrypt(cookieValue); var hmacBytes = GenerateHmac(encryptedCookie, configuration); var hmacString = Convert.ToBase64String(hmacBytes); return(String.Format("{1}{0}", encryptedCookie, hmacString)); }
/// <summary> /// Gets the redirect query string key from <see cref="FormsAuthenticationConfiguration"/> /// </summary> /// <param name="configuration">The forms authentication configuration.</param> /// <returns>Redirect Querystring key</returns> private static string GetRedirectQuerystringKey(CustomAuthenticationConfiguration configuration) { string redirectQuerystringKey = null; if (configuration != null) { redirectQuerystringKey = configuration.RedirectQuerystringKey; } if (string.IsNullOrWhiteSpace(redirectQuerystringKey)) { redirectQuerystringKey = CustomAuthenticationConfiguration.DefaultRedirectQuerystringKey; } return(redirectQuerystringKey); }
/// <summary> /// Builds a cookie for logging a user out /// </summary> /// <param name="configuration">Current configuration</param> /// <returns>Nancy cookie instance</returns> private static INancyCookie BuildLogoutCookie(CustomAuthenticationConfiguration configuration) { var cookie = new NancyCookie(formsAuthenticationCookieName, String.Empty, true, configuration.RequiresSSL, DateTime.Now.AddDays(-1)); if (!string.IsNullOrEmpty(configuration.Domain)) { cookie.Domain = configuration.Domain; } if (!string.IsNullOrEmpty(configuration.Path)) { cookie.Path = configuration.Path; } return(cookie); }
/// <summary> /// Gets the post request hook for redirecting to the login page /// </summary> /// <param name="configuration">Forms authentication configuration to use</param> /// <returns>Post request hook delegate</returns> private static Action <NancyContext> GetRedirectToLoginHook(CustomAuthenticationConfiguration configuration) { return(context => { if (context.Response.StatusCode == HttpStatusCode.Unauthorized) { string redirectQuerystringKey = GetRedirectQuerystringKey(configuration); context.Response = context.GetRedirect( string.Format("{0}?{1}={2}", configuration.RedirectUrl, redirectQuerystringKey, context.ToFullPath("~" + context.Request.Path + HttpUtility.UrlEncode(context.Request.Url.Query)))); } }); }
/// <summary>Enables forms authentication for the application</summary> /// <param name="pipelines">Pipelines to add handlers to (usually "this")</param> /// <param name="configuration">Forms authentication configuration</param> public static void Enable(IPipelines pipelines, CustomAuthenticationConfiguration configuration) { if (pipelines == null) { throw new ArgumentNullException("pipelines"); } if (configuration == null) { throw new ArgumentNullException("configuration"); } if (!configuration.IsValid) { throw new ArgumentException("Configuration is invalid", "configuration"); } CustomAuthenticationProvider.currentConfiguration = configuration; pipelines.BeforeRequest.AddItemToStartOfPipeline(CustomAuthenticationProvider.GetLoadAuthenticationHook(configuration)); if (configuration.DisableRedirect) { return; } pipelines.AfterRequest.AddItemToEndOfPipeline(CustomAuthenticationProvider.GetRedirectToLoginHook(configuration)); }
/// <summary>Enables forms authentication for a module</summary> /// <param name="module">Module to add handlers to (usually "this")</param> /// <param name="configuration">Forms authentication configuration</param> public static void Enable(INancyModule module, CustomAuthenticationConfiguration configuration) { if (module == null) { throw new ArgumentNullException("module"); } if (configuration == null) { throw new ArgumentNullException("configuration"); } if (!configuration.IsValid) { throw new ArgumentException("Configuration is invalid", "configuration"); } module.RequiresAuthentication(); CustomAuthenticationProvider.currentConfiguration = configuration; module.Before.AddItemToStartOfPipeline(CustomAuthenticationProvider.GetLoadAuthenticationHook(configuration)); if (configuration.DisableRedirect) { return; } module.After.AddItemToEndOfPipeline(CustomAuthenticationProvider.GetRedirectToLoginHook(configuration)); }
/// <summary> /// Gets the pre request hook for loading the authenticated user's details /// from the cookie. /// </summary> /// <param name="configuration">Forms authentication configuration to use</param> /// <returns>Pre request hook delegate</returns> private static Func <NancyContext, Response> GetLoadAuthenticationHook(CustomAuthenticationConfiguration configuration) { if (configuration == null) { throw new ArgumentNullException("configuration"); } return(context => { var userGuid = GetAuthenticatedUserFromCookie(context, configuration); if (userGuid != Guid.Empty) { var identity = new UserIdentity(); var plexUsers = configuration.PlexUserRepository.GetAll(); var plexUser = plexUsers.FirstOrDefault(x => Guid.Parse(x.LoginId) == userGuid); if (plexUser != null) { identity.UserName = plexUser.Username; } var localUsers = configuration.LocalUserRepository.GetAll(); var localUser = localUsers.FirstOrDefault(x => Guid.Parse(x.UserGuid) == userGuid); if (localUser != null) { identity.UserName = localUser.UserName; } context.CurrentUser = identity; } return null; }); }
/// <summary> /// Decrypt and validate an encrypted and signed cookie value /// </summary> /// <param name="cookieValue">Encrypted and signed cookie value</param> /// <param name="configuration">Current configuration</param> /// <returns>Decrypted value, or empty on error or if failed validation</returns> public static string DecryptAndValidateAuthenticationCookie(string cookieValue, CustomAuthenticationConfiguration configuration) { var hmacStringLength = Base64Helpers.GetBase64Length(configuration.CryptographyConfiguration.HmacProvider.HmacLength); var encryptedCookie = cookieValue.Substring(hmacStringLength); var hmacString = cookieValue.Substring(0, hmacStringLength); var encryptionProvider = configuration.CryptographyConfiguration.EncryptionProvider; // Check the hmacs, but don't early exit if they don't match var hmacBytes = Convert.FromBase64String(hmacString); var newHmac = GenerateHmac(encryptedCookie, configuration); var hmacValid = HmacComparer.Compare(newHmac, hmacBytes, configuration.CryptographyConfiguration.HmacProvider.HmacLength); var decrypted = encryptionProvider.Decrypt(encryptedCookie); // Only return the decrypted result if the hmac was ok return(hmacValid ? decrypted : string.Empty); }
/// <summary> /// Generate a hmac for the encrypted cookie string /// </summary> /// <param name="encryptedCookie">Encrypted cookie string</param> /// <param name="configuration">Current configuration</param> /// <returns>Hmac byte array</returns> private static byte[] GenerateHmac(string encryptedCookie, CustomAuthenticationConfiguration configuration) { return(configuration.CryptographyConfiguration.HmacProvider.GenerateHmac(encryptedCookie)); }
/// <summary> /// Build the forms authentication cookie /// </summary> /// <param name="userIdentifier">Authenticated user identifier</param> /// <param name="cookieExpiry">Optional expiry date for the cookie (for 'Remember me')</param> /// <param name="configuration">Current configuration</param> /// <returns>Nancy cookie instance</returns> private static INancyCookie BuildCookie(Guid userIdentifier, DateTime?cookieExpiry, CustomAuthenticationConfiguration configuration) { var cookieContents = EncryptAndSignCookie(userIdentifier.ToString(), configuration); var cookie = new NancyCookie(formsAuthenticationCookieName, cookieContents, true, configuration.RequiresSSL, cookieExpiry); if (!string.IsNullOrEmpty(configuration.Domain)) { cookie.Domain = configuration.Domain; } if (!string.IsNullOrEmpty(configuration.Path)) { cookie.Path = configuration.Path; } return(cookie); }