[bcl] Updates referencesource to 4.7.1
[mono-project.git] / mcs / class / referencesource / System.Web / Security / FileAuthorizationModule.cs
blob7eb437ea86e65e2027dc16960e11b21f9973401e
1 //------------------------------------------------------------------------------
2 // <copyright file="FileAuthorizationModule.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
7 /*
8 * FileAclAuthorizationModule class
10 * Copyright (c) 1999 Microsoft Corporation
13 namespace System.Web.Security {
14 using System.Runtime.Serialization;
15 using System.IO;
16 using System.Web;
17 using System.Web.Caching;
18 using System.Web.Util;
19 using System.Web.Configuration;
20 using System.Collections;
21 using System.Collections.Specialized;
22 using System.Security.Principal;
23 using System.Globalization;
24 using System.Security.Permissions;
25 using System.Runtime.InteropServices;
26 using System.Web.Management;
27 using System.Web.Hosting;
28 using System.Collections.Generic;
29 using System.Diagnostics.CodeAnalysis;
33 /// <devdoc>
34 /// <para>
35 /// Verifies that the remote user has NT permissions to access the
36 /// file requested.
37 /// </para>
38 /// </devdoc>
39 public sealed class FileAuthorizationModule : IHttpModule {
42 /// <devdoc>
43 /// <para>
44 /// Initializes a new instance of the <see cref='System.Web.Security.FileAuthorizationModule'/>
45 /// class.
46 /// </para>
47 /// </devdoc>
48 [SecurityPermission(SecurityAction.Demand, UnmanagedCode=true)]
49 public FileAuthorizationModule() {
52 private static bool s_EnabledDetermined;
53 private static bool s_Enabled;
56 [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
57 public static bool CheckFileAccessForUser(String virtualPath, IntPtr token, string verb) {
58 if (virtualPath == null)
59 throw new ArgumentNullException("virtualPath");
60 if (token == IntPtr.Zero)
61 throw new ArgumentNullException("token");
62 if (verb == null)
63 throw new ArgumentNullException("verb");
64 VirtualPath vPath = VirtualPath.Create(virtualPath);
66 if (!vPath.IsWithinAppRoot)
67 throw new ArgumentException(SR.GetString(SR.Virtual_path_outside_application_not_supported), "virtualPath");
69 if (!s_EnabledDetermined) {
70 if (HttpRuntime.UseIntegratedPipeline) {
71 s_Enabled = true; // always enabled in Integrated Mode
73 else {
74 HttpModulesSection modulesSection = RuntimeConfig.GetConfig().HttpModules;
75 int len = modulesSection.Modules.Count;
76 for (int iter = 0; iter < len; iter++) {
77 HttpModuleAction module = modulesSection.Modules[iter];
78 if (Type.GetType(module.Type, false) == typeof(FileAuthorizationModule)) {
79 s_Enabled = true;
80 break;
84 s_EnabledDetermined = true;
86 if (!s_Enabled)
87 return true;
88 ////////////////////////////////////////////////////////////
89 // Step 3: Check the cache for the file-security-descriptor
90 // for the requested file
91 bool freeDescriptor;
92 FileSecurityDescriptorWrapper oSecDesc = GetFileSecurityDescriptorWrapper(vPath.MapPath(), out freeDescriptor);
94 ////////////////////////////////////////////////////////////
95 // Step 4: Check if access is allowed
96 int iAccess = 3;
97 if (verb == "GET" || verb == "POST" || verb == "HEAD" || verb == "OPTIONS")
98 iAccess = 1;
99 bool fAllowed = oSecDesc.IsAccessAllowed(token, iAccess);
101 ////////////////////////////////////////////////////////////
102 // Step 5: Free the security descriptor if adding to cache failed
103 if (freeDescriptor)
104 oSecDesc.FreeSecurityDescriptor();
105 return fAllowed;
110 /// <devdoc>
111 /// <para>[To be supplied.]</para>
112 /// </devdoc>
113 public void Init(HttpApplication app) {
114 app.AuthorizeRequest += new EventHandler(this.OnEnter);
118 /// <devdoc>
119 /// <para>[To be supplied.]</para>
120 /// </devdoc>
121 public void Dispose() {
124 void OnEnter(Object source, EventArgs eventArgs) {
125 if (HttpRuntime.IsOnUNCShareInternal)
126 return; // don't check on UNC shares -- the user token is bogus anyway
127 HttpApplication app;
128 HttpContext context;
130 app = (HttpApplication)source;
131 context = app.Context;
133 if (!IsUserAllowedToFile(context, null)) {
134 context.Response.SetStatusCode(401, subStatus: 3);
135 WriteErrorMessage(context);
136 app.CompleteRequest();
140 internal static bool IsWindowsIdentity(HttpContext context) {
141 return context.User != null &&
142 context.User.Identity != null &&
143 context.User.Identity is WindowsIdentity;
146 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "This method is not dangerous.")]
147 private static bool IsUserAllowedToFile(HttpContext context, string fileName) {
148 ////////////////////////////////////////////////////////////
149 // Step 1: Check if this is WindowsLogin
150 // It's not a windows authenticated user: allow access
151 if (!IsWindowsIdentity(context)) {
152 return true;
155 if (fileName == null) {
156 fileName = context.Request.PhysicalPathInternal;
159 bool isAnonymousUser = (context.User == null || !context.User.Identity.IsAuthenticated);
160 CachedPathData pathData = null;
161 int iAccess = 3;
162 HttpVerb verb = context.Request.HttpVerb;
164 if (verb == HttpVerb.GET
165 || verb == HttpVerb.POST
166 || verb == HttpVerb.HEAD
167 || context.Request.HttpMethod == "OPTIONS")
169 iAccess = 1;
171 ////////////////////////////////////////////////////////////
172 // iff it's a GET or POST or HEAD or OPTIONs verb, we can use the cached result
173 if (!CachedPathData.DoNotCacheUrlMetadata) {
174 pathData = context.GetConfigurationPathData();
175 // as a perf optimization, we cache results for annoymous access
176 // to CachedPathData.PhysicalPath, and avoid doing the full check
177 if (!StringUtil.EqualsIgnoreCase(fileName, pathData.PhysicalPath)) {
178 // set to null so we don't attempt to update it after the full check below
179 pathData = null;
181 else {
182 if (pathData.AnonymousAccessAllowed) { // fast path when everyone has access
183 Debug.Trace("FAM", "IsUserAllowedToFile: pathData.AnonymousAccessAllowed");
184 return true;
186 if (pathData.AnonymousAccessChecked && isAnonymousUser) { // fast path for anonymous user
187 // another thread could be modifying CachedPathData, so return the
188 // value of AnonymousAccessAllowed instead of assuming it is false
189 Debug.Trace("FAM", "IsUserAllowedToFile: pathData.AnonymousAccessChecked && isAnonymousUser");
190 return pathData.AnonymousAccessAllowed;
197 // Step 3: Check the cache for the file-security-descriptor
198 // for the requested file
199 bool freeDescriptor;
200 FileSecurityDescriptorWrapper oSecDesc = GetFileSecurityDescriptorWrapper(fileName, out freeDescriptor);
202 ////////////////////////////////////////////////////////////
203 // Step 4: Check if access is allowed
204 bool fAllowed;
205 if (iAccess == 1) { // iff it's a GET or POST or HEAD or OPTIONs verb, we can cache the result
206 if (oSecDesc._AnonymousAccessChecked && isAnonymousUser) {
207 Debug.Trace("FAM", "IsUserAllowedToFile: oSecDesc._AnonymousAccessChecked && isAnonymousUser");
208 fAllowed = oSecDesc._AnonymousAccess;
210 else {
211 Debug.Trace("FAM", "IsUserAllowedToFile: calling oSecDesc.IsAccessAllowed with iAccess == 1");
212 fAllowed = oSecDesc.IsAccessAllowed(context.WorkerRequest.GetUserToken(), iAccess);
215 if (!oSecDesc._AnonymousAccessChecked && isAnonymousUser) {
216 oSecDesc._AnonymousAccess = fAllowed;
217 oSecDesc._AnonymousAccessChecked = true;
220 // Cache results in CachedPathData if the file exists and annonymous access has been checked.
221 // Note that if CachedPathData.Exists is false, then it does not have a dependency on the file path,
222 // and won't be expunged if the file changes.
223 if (pathData != null && pathData.Exists && oSecDesc._AnonymousAccessChecked) {
224 Debug.Trace("FAM", "IsUserAllowedToFile: updating pathData");
225 pathData.AnonymousAccessAllowed = oSecDesc._AnonymousAccess;
226 pathData.AnonymousAccessChecked = true;
229 else {
230 Debug.Trace("FAM", "IsUserAllowedToFile: calling oSecDesc.IsAccessAllowed with iAccess != 1");
231 fAllowed = oSecDesc.IsAccessAllowed(context.WorkerRequest.GetUserToken(), iAccess); // don't cache this anywhere
234 ////////////////////////////////////////////////////////////
235 // Step 5: Free the security descriptor if adding to cache failed
236 if (freeDescriptor)
237 oSecDesc.FreeSecurityDescriptor();
239 if (fAllowed) {
240 WebBaseEvent.RaiseSystemEvent(null, WebEventCodes.AuditFileAuthorizationSuccess);
242 else {
243 if (!isAnonymousUser)
244 WebBaseEvent.RaiseSystemEvent(null, WebEventCodes.AuditFileAuthorizationFailure);
247 return fAllowed;
249 private static FileSecurityDescriptorWrapper GetFileSecurityDescriptorWrapper(string fileName, out bool freeDescriptor) {
250 if (CachedPathData.DoNotCacheUrlMetadata) {
251 freeDescriptor = true;
252 return new FileSecurityDescriptorWrapper(fileName);
255 freeDescriptor = false;
256 string oCacheKey = CacheInternal.PrefixFileSecurity + fileName;
257 FileSecurityDescriptorWrapper oSecDesc = HttpRuntime.Cache.InternalCache.Get(oCacheKey) as FileSecurityDescriptorWrapper;
259 // If it's not present in the cache, then create it and add to the cache
260 if (oSecDesc == null) {
261 Debug.Trace("FAM", "GetFileSecurityDescriptorWrapper: cache miss for " + fileName);
262 oSecDesc = new FileSecurityDescriptorWrapper(fileName);
263 string cacheDependencyPath = oSecDesc.GetCacheDependencyPath();
264 if (cacheDependencyPath != null) {
265 // Add it to the cache: ignore failures, since a different thread may have added it or the file doesn't exist
266 try {
267 Debug.Trace("FAM", "GetFileSecurityDescriptorWrapper: inserting into cache with dependency on " + cacheDependencyPath);
268 CacheDependency dependency = new CacheDependency(0, cacheDependencyPath);
269 TimeSpan slidingExp = CachedPathData.UrlMetadataSlidingExpiration;
270 HttpRuntime.Cache.InternalCache.Insert(oCacheKey, oSecDesc, new CacheInsertOptions() {
271 Dependencies = dependency,
272 SlidingExpiration = slidingExp,
273 OnRemovedCallback = new CacheItemRemovedCallback(oSecDesc.OnCacheItemRemoved)
275 } catch (Exception e){
276 Debug.Trace("internal", e.ToString());
277 freeDescriptor = true;
281 return oSecDesc;
285 private void WriteErrorMessage(HttpContext context) {
286 if (!context.IsCustomErrorEnabled) {
287 context.Response.Write((new FileAccessFailedErrorFormatter(context.Request.PhysicalPathInternal)).GetErrorMessage(context, false));
288 } else {
289 context.Response.Write((new FileAccessFailedErrorFormatter(null)).GetErrorMessage(context, true));
291 // In Integrated pipeline, ask for handler headers to be generated. This would be unnecessary
292 // if we just threw an access denied exception, and used the standard error mechanism
293 context.Response.GenerateResponseHeadersForHandler();
297 static internal bool RequestRequiresAuthorization(HttpContext context) {
299 Object sec;
300 FileSecurityDescriptorWrapper oSecDesc;
301 string oCacheKey;
303 if (!IsWindowsIdentity(context)) {
304 return false;
307 oCacheKey = CacheInternal.PrefixFileSecurity + context.Request.PhysicalPathInternal;
309 sec = HttpRuntime.Cache.InternalCache.Get(oCacheKey);
311 // If it's not present in the cache, then return true
312 if (sec == null || !(sec is FileSecurityDescriptorWrapper))
313 return true;
315 oSecDesc = (FileSecurityDescriptorWrapper) sec;
316 if (oSecDesc._AnonymousAccessChecked && oSecDesc._AnonymousAccess)
317 return false;
319 return true;
321 internal static bool IsUserAllowedToPath(HttpContext context, VirtualPath virtualPath)
323 return IsUserAllowedToFile(context, virtualPath.MapPath());
327 /////////////////////////////////////////////////////////////////////////////
328 /////////////////////////////////////////////////////////////////////////////
329 /////////////////////////////////////////////////////////////////////////////
330 internal class FileSecurityDescriptorWrapper : IDisposable {
331 ~FileSecurityDescriptorWrapper() {
332 FreeSecurityDescriptor();
335 /////////////////////////////////////////////////////////////////////////////
336 /////////////////////////////////////////////////////////////////////////////
337 internal FileSecurityDescriptorWrapper(String strFile) {
338 _FileName = FileUtil.RemoveTrailingDirectoryBackSlash(strFile);
339 _securityDescriptor = UnsafeNativeMethods.GetFileSecurityDescriptor(_FileName);
342 /////////////////////////////////////////////////////////////////////////////
343 /////////////////////////////////////////////////////////////////////////////
344 internal bool IsAccessAllowed(IntPtr iToken, int iAccess) {
345 if (iToken == IntPtr.Zero)
346 return true;
348 if (_SecurityDescriptorBeingFreed)
349 return IsAccessAllowedUsingNewSecurityDescriptor(iToken, iAccess);
351 _Lock.AcquireReaderLock();
352 try {
353 try {
354 if (!_SecurityDescriptorBeingFreed) {
355 if (_securityDescriptor == IntPtr.Zero)
356 return true;
357 if (_securityDescriptor == UnsafeNativeMethods.INVALID_HANDLE_VALUE)
358 return false;
359 else
360 return (UnsafeNativeMethods.IsAccessToFileAllowed(_securityDescriptor, iToken, iAccess) != 0);
362 } finally {
363 _Lock.ReleaseReaderLock();
365 } catch {
366 throw;
369 return IsAccessAllowedUsingNewSecurityDescriptor(iToken, iAccess);
372 /////////////////////////////////////////////////////////////////////////////
373 /////////////////////////////////////////////////////////////////////////////
374 private bool IsAccessAllowedUsingNewSecurityDescriptor(IntPtr iToken, int iAccess) {
375 if (iToken == IntPtr.Zero)
376 return true;
378 IntPtr secDes = UnsafeNativeMethods.GetFileSecurityDescriptor(_FileName);
379 if (secDes == IntPtr.Zero)
380 return true;
381 if (secDes == UnsafeNativeMethods.INVALID_HANDLE_VALUE)
382 return false;
384 try {
385 try {
386 return (UnsafeNativeMethods.IsAccessToFileAllowed(secDes, iToken, iAccess) != 0);
387 } finally {
388 UnsafeNativeMethods.FreeFileSecurityDescriptor(secDes);
390 } catch {
391 throw;
395 /////////////////////////////////////////////////////////////////////////////
396 /////////////////////////////////////////////////////////////////////////////
397 internal void OnCacheItemRemoved(String key, Object value, CacheItemRemovedReason reason) {
398 FreeSecurityDescriptor();
401 /////////////////////////////////////////////////////////////////////////////
402 /////////////////////////////////////////////////////////////////////////////
403 internal void FreeSecurityDescriptor() {
404 if (!IsSecurityDescriptorValid())
405 return;
406 _SecurityDescriptorBeingFreed = true;
408 _Lock.AcquireWriterLock();
409 try {
410 try {
411 if (!IsSecurityDescriptorValid())
412 return;
413 // VSWHIDBEY 493667: double free in webengine!FreeFileSecurityDescriptor()
414 IntPtr temp = _securityDescriptor;
415 _securityDescriptor = UnsafeNativeMethods.INVALID_HANDLE_VALUE;
416 UnsafeNativeMethods.FreeFileSecurityDescriptor(temp);
417 } finally {
418 _Lock.ReleaseWriterLock();
420 } catch {
421 throw;
425 /////////////////////////////////////////////////////////////////////////////
426 /////////////////////////////////////////////////////////////////////////////
427 internal bool IsSecurityDescriptorValid() {
428 return
429 _securityDescriptor != UnsafeNativeMethods.INVALID_HANDLE_VALUE &&
430 _securityDescriptor != IntPtr.Zero;
433 /////////////////////////////////////////////////////////////////////////////
434 /////////////////////////////////////////////////////////////////////////////
435 internal string GetCacheDependencyPath() {
436 // if security descriptor is invalid, we cannot cache it
437 if (_securityDescriptor == UnsafeNativeMethods.INVALID_HANDLE_VALUE) {
438 Debug.Trace("FAM", "GetCacheDependencyPath: invalid security descriptor");
439 return null;
441 // if security descriptor is valid (file exists), cache it with a dependency on the file name
442 if (_securityDescriptor != IntPtr.Zero) {
443 Debug.Trace("FAM", "GetCacheDependencyPath: valid security descriptor");
444 return _FileName;
446 // file does not exist, but if it's path is beneath the app root, we will cache it and
447 // use the first existing directory as the cache depenedency path
448 string existingDir = FileUtil.GetFirstExistingDirectory(AppRoot, _FileName);
450 #if DBG
451 if (existingDir != null) {
452 Debug.Trace("FAM", "GetCacheDependencyPath: beneath app root");
454 else {
455 Debug.Trace("FAM", "GetCacheDependencyPath: not beneath app root");
457 #endif
458 return existingDir;
461 /////////////////////////////////////////////////////////////////////////////
462 /////////////////////////////////////////////////////////////////////////////
463 private static string AppRoot {
464 get {
465 string appRoot = _AppRoot;
466 if (appRoot == null) {
467 InternalSecurityPermissions.AppPathDiscovery.Assert();
468 appRoot = Path.GetFullPath(HttpRuntime.AppDomainAppPathInternal);
469 appRoot = FileUtil.RemoveTrailingDirectoryBackSlash(appRoot);
471 return appRoot;
475 void IDisposable.Dispose()
477 FreeSecurityDescriptor();
478 GC.SuppressFinalize(this);
480 private IntPtr _securityDescriptor;
481 internal bool _AnonymousAccessChecked = false;
482 internal bool _AnonymousAccess = false;
483 private bool _SecurityDescriptorBeingFreed = false;
484 private string _FileName = null;
485 private ReadWriteSpinLock _Lock = new ReadWriteSpinLock();
486 private static string _AppRoot = null;
489 /////////////////////////////////////////////////////////////////////////////
490 /////////////////////////////////////////////////////////////////////////////
491 /////////////////////////////////////////////////////////////////////////////
492 internal class FileAccessFailedErrorFormatter : ErrorFormatter {
493 private String _strFile;
495 internal FileAccessFailedErrorFormatter(string strFile) {
496 _strFile = strFile;
497 if (_strFile == null)
498 _strFile = String.Empty;
501 protected override string ErrorTitle {
502 get { return SR.GetString(SR.Assess_Denied_Title);}
503 //get { return "Access Denied Error";}
506 protected override string Description {
507 get {
508 return SR.GetString(SR.Assess_Denied_Description3);
509 //return "An error occurred while accessing the resources required to serve this request. &nbsp; This typically happens if you do not have permissions to view the file you are trying to access.";
513 protected override string MiscSectionTitle {
514 get { return SR.GetString(SR.Assess_Denied_Section_Title3); }
515 //get { return "Error message 401.3";}
518 protected override string MiscSectionContent {
519 get {
520 string miscContent;
521 if (_strFile.Length > 0)
522 miscContent = SR.GetString(SR.Assess_Denied_Misc_Content3, HttpRuntime.GetSafePath(_strFile));
523 //return "Access is denied due to NT ACLs on the requested file. Ask the web server's administrator to give you access to "+ _strFile + ".";
524 else
525 miscContent = SR.GetString(SR.Assess_Denied_Misc_Content3_2);
527 AdaptiveMiscContent.Add(miscContent);
528 return miscContent;
532 protected override string ColoredSquareTitle {
533 get { return null;}
536 protected override string ColoredSquareContent {
537 get { return null;}
540 protected override bool ShowSourceFileInfo {
541 get { return false;}