1: // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
2:
3: using System.Diagnostics.CodeAnalysis;
4: using System.Linq;
5: using System.Security.Principal;
6: using System.Web.Mvc.Properties;
7:
8: namespace System.Web.Mvc
9: {
10: [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "Unsealed so that subclassed types can set properties in the default constructor or override our behavior.")]
11: [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
12: public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
13: {
14: private readonly object _typeId = new object();
15:
16: private string _roles;
17: private string[] _rolesSplit = new string[0];
18: private string _users;
19: private string[] _usersSplit = new string[0];
20:
21: public string Roles
22: {
23: get { return _roles ?? String.Empty; }
24: set
25: {
26: _roles = value;
27: _rolesSplit = SplitString(value);
28: }
29: }
30:
31: public override object TypeId
32: {
33: get { return _typeId; }
34: }
35:
36: public string Users
37: {
38: get { return _users ?? String.Empty; }
39: set
40: {
41: _users = value;
42: _usersSplit = SplitString(value);
43: }
44: }
45:
46: // This method must be thread-safe since it is called by the thread-safe OnCacheAuthorization() method.
47: protected virtual bool AuthorizeCore(HttpContextBase httpContext)
48: {
49: if (httpContext == null)
50: {
51: throw new ArgumentNullException("httpContext");
52: }
53:
54: IPrincipal user = httpContext.User;
55: if (!user.Identity.IsAuthenticated)
56: {
57: return false;
58: }
59:
60: if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
61: {
62: return false;
63: }
64:
65: if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole))
66: {
67: return false;
68: }
69:
70: return true;
71: }
72:
73: private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
74: {
75: validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
76: }
77:
78: public virtual void OnAuthorization(AuthorizationContext filterContext)
79: {
80: if (filterContext == null)
81: {
82: throw new ArgumentNullException("filterContext");
83: }
84:
85: if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
86: {
87: // If a child action cache block is active, we need to fail immediately, even if authorization
88: // would have succeeded. The reason is that there's no way to hook a callback to rerun
89: // authorization before the fragment is served from the cache, so we can't guarantee that this
90: // filter will be re-run on subsequent requests.
91: throw new InvalidOperationException(MvcResources.AuthorizeAttribute_CannotUseWithinChildActionCache);
92: }
93:
94: bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)
95: || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true);
96:
97: if (skipAuthorization)
98: {
99: return;
100: }
101:
102: if (AuthorizeCore(filterContext.HttpContext))
103: {
104: // ** IMPORTANT **
105: // Since we're performing authorization at the action level, the authorization code runs
106: // after the output caching module. In the worst case this could allow an authorized user
107: // to cause the page to be cached, then an unauthorized user would later be served the
108: // cached page. We work around this by telling proxies not to cache the sensitive page,
109: // then we hook our custom authorization code into the caching mechanism so that we have
110: // the final say on whether a page should be served from the cache.
111:
112: HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
113: cachePolicy.SetProxyMaxAge(new TimeSpan(0));
114: cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
115: }
116: else
117: {
118: HandleUnauthorizedRequest(filterContext);
119: }
120: }
121:
122: protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
123: {
124: // Returns HTTP 401 - see comment in HttpUnauthorizedResult.cs.
125: filterContext.Result = new HttpUnauthorizedResult();
126: }
127:
128: // This method must be thread-safe since it is called by the caching module.
129: protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext)
130: {
131: if (httpContext == null)
132: {
133: throw new ArgumentNullException("httpContext");
134: }
135:
136: bool isAuthorized = AuthorizeCore(httpContext);
137: return (isAuthorized) ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest;
138: }
139:
140: internal static string[] SplitString(string original)
141: {
142: if (String.IsNullOrEmpty(original))
143: {
144: return new string[0];
145: }
146:
147: var split = from piece in original.Split(',')
148: let trimmed = piece.Trim()
149: where !String.IsNullOrEmpty(trimmed)
150: select trimmed;
151: return split.ToArray();
152: }
153: }
154: }