PageRenderTime 57ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/BlogEngine/DotNetSlave.BusinessLogic/Security/Security.cs

#
C# | 434 lines | 253 code | 57 blank | 124 comment | 33 complexity | eac100cb0bb91b418d65a191d8870be3 MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0, BSD-3-Clause
  1. using System;
  2. using System.Collections.ObjectModel;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Web;
  8. using System.Web.Security;
  9. using System.Diagnostics;
  10. using System.Security;
  11. using System.Security.Principal;
  12. namespace BlogEngine.Core
  13. {
  14. /// <summary>
  15. /// Class to provide a unified area of authentication/authorization checking.
  16. /// </summary>
  17. public partial class Security : IHttpModule
  18. {
  19. static Security()
  20. {
  21. }
  22. /// <summary>
  23. /// Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"/>.
  24. /// </summary>
  25. public void Dispose()
  26. {
  27. // Nothing to dispose
  28. }
  29. /// <summary>
  30. /// Initializes a module and prepares it to handle requests.
  31. /// </summary>
  32. /// <param name="context">An <see cref="T:System.Web.HttpApplication"/> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application</param>
  33. public void Init(HttpApplication context)
  34. {
  35. context.AuthenticateRequest += ContextAuthenticateRequest;
  36. }
  37. /// <summary>
  38. /// Handles the AuthenticateRequest event of the context control.
  39. /// </summary>
  40. /// <param name="sender">
  41. /// The source of the event.
  42. /// </param>
  43. /// <param name="e">
  44. /// The <see cref="System.EventArgs"/> instance containing the event data.
  45. /// </param>
  46. private static void ContextAuthenticateRequest(object sender, EventArgs e)
  47. {
  48. var context = ((HttpApplication)sender).Context;
  49. // FormsAuthCookieName is a custom cookie name based on the current instance.
  50. HttpCookie authCookie = context.Request.Cookies[FormsAuthCookieName];
  51. if (authCookie != null)
  52. {
  53. Blog blog = Blog.CurrentInstance;
  54. FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
  55. // for extra security, make sure the UserData matches the current blog instance.
  56. // this would prevent a cookie name change for a forms auth cookie encrypted in
  57. // the same application (different blog) as being valid for this blog instance.
  58. if (authTicket != null && !string.IsNullOrWhiteSpace(authTicket.UserData) && authTicket.UserData.Equals(Blog.CurrentInstance.Id.ToString(), StringComparison.OrdinalIgnoreCase))
  59. {
  60. CustomIdentity identity = new CustomIdentity(authTicket.Name, true);
  61. CustomPrincipal principal = new CustomPrincipal(identity);
  62. context.User = principal;
  63. return;
  64. }
  65. }
  66. // need to create an empty/unauthenticated user to assign to context.User.
  67. CustomIdentity unauthIdentity = new CustomIdentity(string.Empty, false);
  68. CustomPrincipal unauthPrincipal = new CustomPrincipal(unauthIdentity);
  69. context.User = unauthPrincipal;
  70. }
  71. /// <summary>
  72. /// Name of the Forms authentication cookie for the current blog instance.
  73. /// </summary>
  74. public static string FormsAuthCookieName
  75. {
  76. get
  77. {
  78. return FormsAuthentication.FormsCookieName + "-" + Blog.CurrentInstance.Id.ToString();
  79. }
  80. }
  81. public static void SignOut()
  82. {
  83. // using a custom cookie name based on the current blog instance.
  84. HttpCookie cookie = new HttpCookie(FormsAuthCookieName, string.Empty);
  85. cookie.Expires = DateTime.Now.AddYears(-3);
  86. HttpContext.Current.Response.Cookies.Add(cookie);
  87. }
  88. public static bool AuthenticateUser(string username, string password, bool rememberMe)
  89. {
  90. string un = (username ?? string.Empty).Trim();
  91. string pw = (password ?? string.Empty).Trim();
  92. if (!string.IsNullOrWhiteSpace(un) && !string.IsNullOrWhiteSpace(pw))
  93. {
  94. bool isValidated = Membership.ValidateUser(un, pw);
  95. if (isValidated)
  96. {
  97. HttpContext context = HttpContext.Current;
  98. DateTime expirationDate = DateTime.Now.Add(FormsAuthentication.Timeout);
  99. FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
  100. 1,
  101. un,
  102. DateTime.Now,
  103. expirationDate,
  104. rememberMe,
  105. Blog.CurrentInstance.Id.ToString(),
  106. FormsAuthentication.FormsCookiePath
  107. );
  108. string encryptedTicket = FormsAuthentication.Encrypt(ticket);
  109. // setting a custom cookie name based on the current blog instance.
  110. // if !rememberMe, set expires to DateTime.MinValue which makes the
  111. // cookie a browser-session cookie expiring when the browser is closed.
  112. HttpCookie cookie = new HttpCookie(FormsAuthCookieName, encryptedTicket);
  113. cookie.Expires = rememberMe ? expirationDate : DateTime.MinValue;
  114. context.Response.Cookies.Set(cookie);
  115. string returnUrl = context.Request.QueryString["returnUrl"];
  116. // ignore Return URLs not beginning with a forward slash, such as remote sites.
  117. if (string.IsNullOrWhiteSpace(returnUrl) || !returnUrl.StartsWith("/"))
  118. returnUrl = null;
  119. if (!string.IsNullOrWhiteSpace(returnUrl))
  120. {
  121. context.Response.Redirect(returnUrl);
  122. }
  123. else
  124. {
  125. context.Response.Redirect(Utils.RelativeWebRoot);
  126. }
  127. return true;
  128. }
  129. }
  130. return false;
  131. }
  132. #region "Properties"
  133. /// <summary>
  134. /// If the current user is authenticated, returns the current MembershipUser. If not, returns null. This is just a shortcut to Membership.GetUser().
  135. /// </summary>
  136. public static MembershipUser CurrentMembershipUser
  137. {
  138. get
  139. {
  140. return Membership.GetUser();
  141. }
  142. }
  143. /// <summary>
  144. /// Gets the current user for the current HttpContext.
  145. /// </summary>
  146. /// <remarks>
  147. /// This should always return HttpContext.Current.User. That value and Thread.CurrentPrincipal can't be
  148. /// guaranteed to always be the same value, as they can be set independently from one another. Looking
  149. /// through the .Net source, the System.Web.Security.Roles class also returns the HttpContext's User.
  150. /// </remarks>
  151. public static System.Security.Principal.IPrincipal CurrentUser
  152. {
  153. get
  154. {
  155. return HttpContext.Current.User;
  156. }
  157. }
  158. /// <summary>
  159. /// Gets whether the current user is logged in.
  160. /// </summary>
  161. public static bool IsAuthenticated
  162. {
  163. get
  164. {
  165. return Security.CurrentUser.Identity.IsAuthenticated;
  166. }
  167. }
  168. /// <summary>
  169. /// Gets whether the currently logged in user is in the administrator role.
  170. /// </summary>
  171. public static bool IsAdministrator
  172. {
  173. get
  174. {
  175. return (Security.IsAuthenticated && Security.CurrentUser.IsInRole(BlogConfig.AdministratorRole));
  176. }
  177. }
  178. /// <summary>
  179. /// Returns an IEnumerable of Rights that belong to the ecurrent user.
  180. /// </summary>
  181. /// <returns></returns>
  182. public static IEnumerable<Right> CurrentUserRights()
  183. {
  184. return Right.GetRights(Security.GetCurrentUserRoles());
  185. }
  186. #endregion
  187. #region "Public Methods"
  188. /// <summary>
  189. /// If the current user does not have the requested right, either redirects to the login page,
  190. /// or throws a SecurityException.
  191. /// </summary>
  192. /// <param name="right"></param>
  193. /// <param name="redirectToLoginPage">
  194. /// If true and user does not have rights, redirects to the login page.
  195. /// If false and user does not have rights, throws a security exception.
  196. /// </param>
  197. public static void DemandUserHasRight(Rights right, bool redirectToLoginPage)
  198. {
  199. DemandUserHasRight(AuthorizationCheck.HasAny, redirectToLoginPage, new[] { right });
  200. }
  201. /// <summary>
  202. /// If the current user does not have the requested rights, either redirects to the login page,
  203. /// or throws a SecurityException.
  204. /// </summary>
  205. /// <param name="authCheck"></param>
  206. /// <param name="redirectIfUnauthorized">
  207. /// If true and user does not have rights, redirects to the login page or homepage.
  208. /// If false and user does not have rights, throws a security exception.
  209. /// </param>
  210. /// <param name="rights"></param>
  211. public static void DemandUserHasRight(AuthorizationCheck authCheck, bool redirectIfUnauthorized, params Rights[] rights)
  212. {
  213. if (!IsAuthorizedTo(authCheck, rights))
  214. {
  215. if (redirectIfUnauthorized)
  216. {
  217. RedirectForUnauthorizedRequest();
  218. }
  219. else
  220. {
  221. throw new SecurityException("User doesn't have the right to perform this");
  222. }
  223. }
  224. }
  225. public static void RedirectForUnauthorizedRequest()
  226. {
  227. HttpContext context = HttpContext.Current;
  228. Uri referrer = context.Request.UrlReferrer;
  229. bool isFromLoginPage = referrer != null && referrer.LocalPath.IndexOf("/Account/login.aspx", StringComparison.OrdinalIgnoreCase) != -1;
  230. // If the user was just redirected from the login page to the current page,
  231. // we will then redirect them to the homepage, rather than back to the
  232. // login page to prevent confusion.
  233. if (isFromLoginPage)
  234. {
  235. context.Response.Redirect(Utils.RelativeWebRoot);
  236. }
  237. else
  238. {
  239. context.Response.Redirect(string.Format("{0}Account/login.aspx?ReturnURL={1}", Utils.RelativeWebRoot, HttpUtility.UrlPathEncode(context.Request.RawUrl)));
  240. }
  241. }
  242. /// <summary>
  243. /// Returns whether or not the current user has the passed in Right.
  244. /// </summary>
  245. /// <param name="right"></param>
  246. /// <returns></returns>
  247. public static bool IsAuthorizedTo(Rights right)
  248. {
  249. return Right.HasRight(right, Security.GetCurrentUserRoles());
  250. }
  251. /// <summary>
  252. /// Returns whether the current user passes authorization on the rights based on the given AuthorizationCheck.
  253. /// </summary>
  254. /// <param name="authCheck"></param>
  255. /// <param name="rights"></param>
  256. /// <returns></returns>
  257. public static bool IsAuthorizedTo(AuthorizationCheck authCheck, IEnumerable<Rights> rights)
  258. {
  259. if (rights.Count() == 0)
  260. {
  261. // Always return false for this. If there's a mistake where authorization
  262. // is being checked for on an empty collection, we don't want to return
  263. // true.
  264. return false;
  265. }
  266. else
  267. {
  268. var roles = Security.GetCurrentUserRoles();
  269. if (authCheck == AuthorizationCheck.HasAny)
  270. {
  271. foreach (var right in rights)
  272. {
  273. if (Right.HasRight(right, roles))
  274. {
  275. return true;
  276. }
  277. }
  278. return false;
  279. }
  280. else if (authCheck == AuthorizationCheck.HasAll)
  281. {
  282. bool authCheckPassed = true;
  283. foreach (var right in rights)
  284. {
  285. if (!Right.HasRight(right, roles))
  286. {
  287. authCheckPassed = false;
  288. break;
  289. }
  290. }
  291. return authCheckPassed;
  292. }
  293. else
  294. {
  295. throw new NotSupportedException();
  296. }
  297. }
  298. }
  299. /// <summary>
  300. /// Returns whether a role is a System role.
  301. /// </summary>
  302. /// <param name="roleName">The name of the role.</param>
  303. /// <returns>true if the roleName is a system role, otherwiser false</returns>
  304. public static bool IsSystemRole(string roleName)
  305. {
  306. if (roleName.Equals(BlogConfig.AdministratorRole, StringComparison.OrdinalIgnoreCase) ||
  307. roleName.Equals(BlogConfig.AnonymousRole, StringComparison.OrdinalIgnoreCase) ||
  308. roleName.Equals(BlogConfig.EditorsRole, StringComparison.OrdinalIgnoreCase))
  309. {
  310. return true;
  311. }
  312. return false;
  313. }
  314. /// <summary>
  315. /// Returns whether the current user passes authorization on the rights based on the given AuthorizationCheck.
  316. /// </summary>
  317. /// <param name="authCheck"></param>
  318. /// <param name="rights"></param>
  319. /// <returns></returns>
  320. public static bool IsAuthorizedTo(AuthorizationCheck authCheck, params Rights[] rights)
  321. {
  322. return IsAuthorizedTo(authCheck, rights.ToList());
  323. }
  324. #endregion
  325. #region "Methods"
  326. /// <summary>
  327. /// Helper method that returns the correct roles based on authentication.
  328. /// </summary>
  329. /// <returns></returns>
  330. public static string[] GetCurrentUserRoles()
  331. {
  332. if (!IsAuthenticated)
  333. {
  334. // This needs to be recreated each time, because it's possible
  335. // that the array can fall into the wrong hands and then someone
  336. // could alter it.
  337. return new[] { BlogConfig.AnonymousRole };
  338. }
  339. else
  340. {
  341. return Roles.GetRolesForUser();
  342. }
  343. }
  344. /// <summary>
  345. /// Impersonates a user for the duration of the HTTP request.
  346. /// </summary>
  347. /// <param name="username">The username</param>
  348. /// <param name="password">The password</param>
  349. /// <returns>True if the credentials are correct and impersonation succeeds</returns>
  350. public static bool ImpersonateUser(string username, string password)
  351. {
  352. if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
  353. return false;
  354. CustomIdentity identity = new CustomIdentity(username, password);
  355. if (!identity.IsAuthenticated) { return false; }
  356. CustomPrincipal principal = new CustomPrincipal(identity);
  357. // Make the custom principal be the user for the rest of this request.
  358. HttpContext.Current.User = principal;
  359. return true;
  360. }
  361. #endregion
  362. }
  363. /// <summary>
  364. /// Enum for setting how rights should be checked for.
  365. /// </summary>
  366. public enum AuthorizationCheck
  367. {
  368. /// <summary>
  369. /// A user will be considered authorized if they have any of the given Rights.
  370. /// </summary>
  371. HasAny,
  372. /// <summary>
  373. /// A user will be considered authorized if they have all of the given Rights.
  374. /// </summary>
  375. HasAll
  376. }
  377. }