AppSecurityManager.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / MS / Internal / AppModel / AppSecurityManager.cs / 1407647 / AppSecurityManager.cs

                            //------------------------------------------------------------------------------ 
//  Microsoft Avalon
//  Copyright (c) Microsoft Corporation, 2001
//
//  File:           appsecuritymanager.cs 
//
//  Description:    AppSecurityManager class. 
// 
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// IMPORTANT: We are creating an instance of IInternetSecurityManager here. This 
// is currently also done in the CustomCredentialPolicy at the Core level. Any
// modification to either of these classes--especially concerning MapUrlToZone--
// should be considered for both classes.
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
//
//  History:        04-24-02 - [....] - created 
//----------------------------------------------------------------------------- 

using System; 
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.Security; 
using System.Security.Permissions;
using Microsoft.Win32; 
using System.IO.Packaging; 
using System.Windows;
using System.Windows.Interop; 
using MS.Internal.Utility;
using MS.Win32;
using System.Runtime.InteropServices;
using MS.Internal.Documents.Application; 

using SecurityHelper=MS.Internal.SecurityHelper; 
 
namespace MS.Internal.AppModel
{ 

    enum LaunchResult
    {
        Launched, 
        NotLaunched,
        NotLaunchedDueToPrompt 
    }; 

    internal static class AppSecurityManager 
    {
        #region Internal Methods

 
        ///
        ///     Safely launch the browser if you can. 
        ///     If you can't demand unmanaged code permisison. 
        ///
        ///     originatingUri = the current uri 
        ///     destinationUri = the uri you are going to.
        ///
        ///
        /// Critical - gets access to critical resource (uri), calls critical code (launch browser) 
        /// TreatAsSafe because
        ///                     we consider navigates to http or http to the top-level browser as safe. 
        ///                     we consider navigates to mailto as safe. 
        ///                     for all other cases - we demand Unmanaged Code Permission
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        internal static void SafeLaunchBrowserDemandWhenUnsafe( Uri originatingUri, Uri destinationUri, bool fIsTopLevel )
        {
            LaunchResult launched = LaunchResult.NotLaunched; 

            launched = SafeLaunchBrowserOnlyIfPossible( originatingUri, destinationUri, fIsTopLevel ) ; 
            if ( launched == LaunchResult.NotLaunched ) 
            {
                SecurityHelper.DemandUnmanagedCode(); 
                UnsafeLaunchBrowser( destinationUri );
            }
        }
 
        ///
        /// Safely launch the browser if it's possible to do so in partial trust 
        /// Returns enum indicating whether we safely launched ( or at least think we did). 
        ///
        ///     This function is appropriate for use when we launch the browser from partial trust 
        ///     ( as it doesn't perform demands for the "unsafe" cases )
        ///
        internal static LaunchResult SafeLaunchBrowserOnlyIfPossible(Uri originatingUri, Uri destinationUri, bool fIsTopLevel)
        { 
            return SafeLaunchBrowserOnlyIfPossible(originatingUri, destinationUri, null, fIsTopLevel);
        } 
 
        ///
        /// Safely launch the browser if it's possible to do so in partial trust 
        /// Returns enum indicating whether we safely launched ( or at least think we did).
        /// Html target names can be passed in with this.
        ///     This function is appropriate for use when we launch the browser from partial trust
        ///     ( as it doesn't perform demands for the "unsafe" cases ) 
        ///
        /// 
        /// Critical - gets access to critical resource (uri), calls critical code (launch browser) 
        ///
        /// TreatAsSafe because 
        ///                     we consider navigates to http or http to the top-level browser as safe.
        ///                     we consider navigates to mailto as safe.
        ///
        ///                     for all other cases - we don't launch the browser - and return a result 
        ///                     indicating that we didn't launch.
        /// 
        [SecurityCritical, SecurityTreatAsSafe] 
        internal static LaunchResult SafeLaunchBrowserOnlyIfPossible(Uri originatingUri, Uri destinationUri, string targetName, bool fIsTopLevel )
        { 
            LaunchResult launched = LaunchResult.NotLaunched ;

            bool isKnownScheme =  (Object.ReferenceEquals(destinationUri.Scheme, Uri.UriSchemeHttp))  ||
                                   (Object.ReferenceEquals(destinationUri.Scheme, Uri.UriSchemeHttps)) || 
                                   destinationUri.IsFile;
 
            bool fIsMailTo = String.Compare(destinationUri.Scheme, Uri.UriSchemeMailto, StringComparison.OrdinalIgnoreCase) == 0 ; 

            // We elevate to navigate the browser iff: 
            //  We are user initiated AND
            //      Scheme == http/https & topLevel OR scheme == mailto.
            //
            // For all other cases ( evil protocols etc). 
            // We will demand.
            // 
            // The check of IsInitialViewerNavigation is necessary because viewer applications will probably 
            // need to call Navigate on the URI they receive, but we want them to be able to do it in partial trust.
            if ((!BrowserInteropHelper.IsInitialViewerNavigation && 
                MS.Internal.PresentationFramework.SecurityHelper.CallerHasUserInitiatedNavigationPermission()) &&
                  ((fIsTopLevel && isKnownScheme) || fIsMailTo) )
            {
                if (isKnownScheme) 
                {
                    IBrowserCallbackServices ibcs = ( Application.Current != null ) ? Application.Current.BrowserCallbackServices : null ; 
                    if (ibcs != null) 
                    {
                        launched = CanNavigateToUrlWithZoneCheck(originatingUri , destinationUri); 
                        if ( launched == LaunchResult.Launched )
                        {
                            // resetting launched to NotLaunched here; if the assert succeeds
                            // and ibcs.DelegateNavigation does not throw then we will set it to Launched. 
                            launched = LaunchResult.NotLaunched;
                            // Browser app. 
                            // 

                            ibcs.DelegateNavigation( BindUriHelper.UriToString( destinationUri ), targetName, GetHeaders(destinationUri)); 

                            launched = LaunchResult.Launched ;
                        }
                    } 
                }
                else if ( fIsMailTo ) // unnecessary if - but being paranoid. 
                { 
                    // Shell-Exec the browser to the mailto url.
                    // assumed safe - because we're only allowing this for mailto urls. 
                    //

                    UnsafeNativeMethods.ShellExecute( new HandleRef( null, IntPtr.Zero) , /*hwnd*/
                                                      null, /*operation*/ 
                                                      BindUriHelper.UriToString( destinationUri ), /*file*/
                                                      null, /*parameters*/ 
                                                      null, /*directory*/ 
                                                      0 ); /*nShowCmd*/
                    launched = LaunchResult.Launched ; 
                }
            }

            return launched ; 
        }
 
        // This invokes the browser unsafely. 
        // Whoever is calling this function should do the right demands.
        ///  
        /// Critical - gets access to critical resource (uri and browsercallback services), calls critical code (launch browser)
        /// 
        [SecurityCritical]
        internal static void UnsafeLaunchBrowser(Uri uri, string targetFrame = null) 
        {
            // This'll likely go into SafeLaunchBrowser() function. 
            if (Application.Current != null && Application.Current.CheckAccess()) 
            {
                IBrowserCallbackServices ibcs = Application.Current.BrowserCallbackServices; 
                if (ibcs != null)
                {
                    // Browser app.
                    // 

                    ibcs.DelegateNavigation(BindUriHelper.UriToString(uri), targetFrame, GetHeaders(uri)); 
                    return; 
                }
            } 

            ShellExecuteDefaultBrowser(uri);
        }
 
        /// 
        /// Opens the default browser for the passed in Uri. 
        ///  
        /// 
        /// Critical - calls critical code (ShellExecuteEx) 
        /// 
        [SecurityCritical]
        internal static void ShellExecuteDefaultBrowser(Uri uri)
        { 
            UnsafeNativeMethods.ShellExecuteInfo sei = new UnsafeNativeMethods.ShellExecuteInfo();
            sei.cbSize = Marshal.SizeOf(sei); 
            sei.fMask = UnsafeNativeMethods.ShellExecuteFlags.SEE_MASK_FLAG_DDEWAIT; 
            /*
            There is a bug on Windows Vista (with IE 7): ShellExecute via SEE_MASK_CLASSNAME fails for an 
            http[s]:// URL. It works fine for file://. The cause appears to be that the DDE command template
            defined in HKCR\IE.AssocFile.HTM\shell\opennew\ddeexec is used: [file://%1",-1,,,,,]. On XP, the
            the key used is (supposedly) HKCR\htmlfile\shell\opennew\ddeexec, and its value is ["%1",,-1,0,,,,].
            The workaround here is to add the SEE_MASK_CLASSNAME flag only for non-HTTP URLs. For HTTP, 
            "plain" ShellExecute just works, incl. with Firefox/Netscape as the default browser.
            */ 
            if (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps) 
            {
                sei.fMask |= UnsafeNativeMethods.ShellExecuteFlags.SEE_MASK_CLASSNAME; 
                sei.lpClass = ".htm"; // The default browser is looked up by this.
            }
            sei.lpFile = uri.ToString(); // It's safe to use Uri.ToString since there's an inheritance demand on it that prevents spoofing by subclasses.
            if (!UnsafeNativeMethods.ShellExecuteEx(sei)) 
                throw new InvalidOperationException(SR.Get(SRID.FailToLaunchDefaultBrowser),
                    new System.ComponentModel.Win32Exception(/*uses the last Win32 error*/)); 
        } 

        #endregion Internal Methods 

        #region Private Methods

        ///  
        /// Returns the HTTP "Referer" header.
        ///  
        /// returns a string containing one or more HTTP headers separated by \r\n; the string must also be terminated with a \r\n 
        private static string GetHeaders(Uri destinationUri)
        { 
            string referer = BindUriHelper.GetReferer(destinationUri);

            if (!String.IsNullOrEmpty(referer))
            { 
                // The headers we pass in to IWebBrowser2.Navigate must
                // be terminated with a \r\n because the browser then 
                // concatenates its own headers on to the end of that string. 
                referer = RefererHeader + referer + "\r\n";
            } 

            return referer;
        }
 
        //
        // Functionally equivalent copy of Trident's CanNavigateToUrlWithZoneCheck function 
        //  Checks to see whether a navigation is considered a zone elevation. 
        //  Once a zone elevation is identified - calls into urlmon to check settings.
        // 
        ///
        ///     Critical - performs elevations to call IsFeatureEnabled; call critical method MUTZ
        ///
        ///     TreatAsSafe - information disclosed is whether a navigation is "safe" or not. 
        ///                   considered ok to give out. you will be able to get this anyway by trapping exceptions
        ///                   or seeing whether a navigation succeeded/failed. 
        /// 
        [SecurityCritical, SecurityTreatAsSafe ]
        private static LaunchResult CanNavigateToUrlWithZoneCheck( Uri originatingUri, Uri destinationUri ) 
        {
            LaunchResult launchResult = LaunchResult.NotLaunched ; // fail securely - assume this is the default.
            int targetZone = NativeMethods.URLZONE_LOCAL_MACHINE ; // fail securely this is the most priveleged zone
            int sourceZone = NativeMethods.URLZONE_INTERNET ; // fail securely this is the least priveleged zone. 
            bool fEnabled = true;
 
            EnsureSecurityManager(); 

            // is this feature enabled ? 
            fEnabled  =  UnsafeNativeMethods.CoInternetIsFeatureEnabled(
                                        NativeMethods.FEATURE_ZONE_ELEVATION,
                                        NativeMethods.GET_FEATURE_FROM_PROCESS) != NativeMethods.S_FALSE ;
 
            targetZone = MapUrlToZone(destinationUri);
 
            // Get source zone. 

            // Initialize sourceUri to null so that source zone defaults to the least privileged zone. 
            Uri sourceUri = null;

            // If the MimeType is not a container, attempt to find sourceUri.
            // sourceUri should be null for Container cases, since it always assumes 
            // the least privileged zone (InternetZone).
            if (Application.Current.MimeType != MimeType.Document) 
            { 
                sourceUri = BrowserInteropHelper.Source;
            } 
            else if (destinationUri.IsFile &&
                System.IO.Path.GetExtension(destinationUri.LocalPath)
                    .Equals(DocumentStream.XpsFileExtension, StringComparison.OrdinalIgnoreCase))
            { 
                // In this case we know the following:
                //  1) We are currently a Container 
                //  2) The destination is a File and another Container 

                // In this case we want to treat the destination as internet too so Container 
                // can navigate to other Containers by passing zone checks
                targetZone = NativeMethods.URLZONE_INTERNET;
            }
 
            if ( sourceUri != null )
            { 
                sourceZone = MapUrlToZone(sourceUri); 
            }
            else 
            {
                // 2 potential ways to get here.
                //      a) We aren't a fusion hosted app. Assume full-trust.
                //      b) Some bug in hosting caused source Uri to be null. 
                //
                // For a - we will say there is no cross-domain check. 
                //     b - we'll assume InternetZone, and use Source. 

                bool fTrusted = SecurityHelper.CheckUnmanagedCodePermission(); 

                if ( fTrusted )
                {
                    return LaunchResult.Launched; 
                }
                else 
                { 
                    //
                    //  If we didn't get a SourceUri, we'll assume internet zone. 
                    //  And use Source for the uri of origin.
                    //
                    //  This isn't quite right - but the sourceUri is only used to show a message to the user.
                    //  Worse case is confusing user experience. ( this uri is not used in the elevation determination). 
                    //
 
                    sourceZone = NativeMethods.URLZONE_INTERNET ; 
                    sourceUri = originatingUri ;
                } 
            }

            // 
            // ------------------------------ 
            // Check if there is a zone elevation.
            // Custom zones would have a higher value that URLZONE_UNTRUSTED, so this solution isn't quite complete. 
            // However, we don't know of any product actively using custom zones, so rolling this out. 
            // INTRANET and TRUSTED are treated as equals.
            // ------------------------------ 
            // 

            //
            // Note the negative logic - it first sees to see something is *not* a zone elevation. 
            //
            if ( 
                    // Even if feature is disabled. 
                    // We still block navigation to local machine.
                    // IF source zone is internet or restricted. 

                    (!fEnabled &&
                    ((sourceZone != NativeMethods.URLZONE_INTERNET &&
                       sourceZone != NativeMethods.URLZONE_UNTRUSTED) || 
                       targetZone != NativeMethods.URLZONE_LOCAL_MACHINE)) ||
 
                    // If feature is enabled 
                    // It's not a zone elevation if
                    //     the zones are equal OR 
                    //        the zones are both less than restricted and
                    //            the sourceZone is more trusted than Target OR
                    //            sourceZone and TargetZone are both Intranet or Trusted
                    // 
                    // per aganjam - Intranet and Trusted are treated as equivalent
                    // as it was a common scenario for IE. ( website on intranet points to trusted site). 
                    // 

                    (fEnabled && 
                    (
                        sourceZone == targetZone ||
                        (
                            sourceZone <= NativeMethods.URLZONE_UNTRUSTED && 
                            targetZone <= NativeMethods.URLZONE_UNTRUSTED &&
                            ( 
                                sourceZone < targetZone || 
                                (
                                    (sourceZone == NativeMethods.URLZONE_TRUSTED || sourceZone == NativeMethods.URLZONE_INTRANET) && 
                                    (targetZone == NativeMethods.URLZONE_TRUSTED || targetZone == NativeMethods.URLZONE_INTRANET)
                                )
                            )
                        ) 
                        )))
            { 
                // There is no zone elevation. You can launch away ! 
                return LaunchResult.Launched ;
            } 

            launchResult = CheckBlockNavigation( sourceUri ,
                                                  destinationUri,
                                                  fEnabled ) ; 

            return launchResult ; 
        } 

        /// 
        ///     Called when we suspect there is a zone elevation.
        ///     Calls the Urlmon IsFeatureZoneElevationEnabled which may pop UI based on settings.
        /// functionally equivalent to the BlockNavigation: label in Trident's CanNavigateToUrlWithZoneCheck
        /// 
        ///
        ///     Critical - calls a function that has a SUC on it. ( CoIntenrnetIsFeatureZoneElevationEnabled) 
        /// 
        [SecurityCritical]
        private static LaunchResult CheckBlockNavigation(Uri originatingUri, Uri destinationUri, bool fEnabled ) 
        {

            if (fEnabled)
            { 
                if( UnsafeNativeMethods.CoInternetIsFeatureZoneElevationEnabled(
                            BindUriHelper.UriToString( originatingUri ) , 
                            BindUriHelper.UriToString( destinationUri ) , 
                            _secMgr,
                            NativeMethods.GET_FEATURE_FROM_PROCESS) == NativeMethods.S_FALSE) 
                {
                    return LaunchResult.Launched;
                }
                else 
                {
                    if ( IsZoneElevationSettingPrompt( destinationUri ) ) 
                    { 
                        // url action is query, and we got a "no" answer back.
                        // we can assume user responded No. 
                        return LaunchResult.NotLaunchedDueToPrompt ;
                    }
                    else
                        return LaunchResult.NotLaunched ; 
                }
            } 
            else 
            {
                return LaunchResult.Launched ; 
            }
        }

        // Is ZoneElevation setting set to prompt ? 
        ///
        ///     Critical - elevates to call ProcessUrlAction. 
        ///     TreatAsSafe - information return indicates whether we will prompt for the current zone. 
        ///                   considered ok to expose.
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        private static bool IsZoneElevationSettingPrompt( Uri target )
        {
            Invariant.Assert(_secMgr != null ); 

            // Was this due to a prompt ? 
 
            int policy = NativeMethods.URLPOLICY_DISALLOW ;
 
            unsafe
            {
                String targetString = BindUriHelper.UriToString( target ) ;
                new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); // BlessedAssert: 
                try
                { 
                    _secMgr.ProcessUrlAction( targetString, 
                                              NativeMethods.URLACTION_FEATURE_ZONE_ELEVATION ,
                                              (byte*) & policy , 
                                              Marshal.SizeOf( typeof (int) ),
                                              null,
                                              0,
                                              NativeMethods.PUAF_NOUI, 
                                              0 );
                } 
                finally 
                {
                    CodeAccessPermission.RevertAssert(); 
                }
            }

            return ( policy == NativeMethods.URLPOLICY_QUERY ) ; 
        }
 
        /// 
        /// Critical - elevates to call unmanaged code to set the security site.
        ///     The SecurityManager is a critical resource. 
        /// Safe: The Security Manager is used only within this class (not exposed). Just creating it has
        ///     no observable side effects.
        ///
        [SecurityCritical, SecurityTreatAsSafe] 
        private static void EnsureSecurityManager()
        { 
            // IMPORTANT: See comments in header r.e. IInternetSecurityManager 

            if( _secMgr == null ) 
            {
                lock( _lockObj )
                {
                    if ( _secMgr == null ) // null check again - now that we're in the lock. 
                    {
                          _secMgr = (UnsafeNativeMethods.IInternetSecurityManager) new InternetSecurityManager(); 
 
                          //
                         // Set the Security Manager Site. 
                         // This enables any dialogs popped to be modal to our window.
                         //
                        _secMgrSite = new SecurityMgrSite();
                        new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); // BlessedAssert: 
                        try
                        { 
                             _secMgr.SetSecuritySite( (NativeMethods.IInternetSecurityMgrSite) _secMgrSite ) ; 
                        }
                        finally 
                        {
                            CodeAccessPermission.RevertAssert();
                        }
                    } 
                }
            } 
        } 

 
        ///
        /// Critical - elevates to call SetSecuritySite.
        /// TreatAsSafe - clearing the security site is considered safe.
        ///               worse that can happen is any urlmon prompts will be non-modal. 
        ///
 
        [SecurityCritical, SecurityTreatAsSafe ] 
        internal static void ClearSecurityManager()
        { 
            if (_secMgr != null )
            {
                new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();  // BlessedAssert:
                try 
                {
                    lock ( _lockObj ) 
                    { 
                        if ( _secMgr != null )
                        { 
                            _secMgr.SetSecuritySite( null ) ;
                            _secMgrSite = null ;
                            _secMgr = null ;
                        } 
                    }
                } 
                finally 
                {
                    CodeAccessPermission.RevertAssert(); 
                }


            } 
        }
 
        /// 
        /// Critical - Calls the COM method. A URL's security zone is not a big secret, and in most cases it
        ///     can be inferred by just parsing the URL, but it's still information obtained under elevation, 
        ///     and thus we shouldn't leak it without a good reason.
        ///
        [SecurityCritical]
        internal static int MapUrlToZone(Uri url) 
        {
            EnsureSecurityManager(); 
            int zone; 
            _secMgr.MapUrlToZone(BindUriHelper.UriToString(url), out zone, 0);
            return zone; 
        }

        [ComImport, ComVisible(false), Guid("7b8a2d94-0ac9-11d1-896c-00c04Fb6bfc4")]
        internal class InternetSecurityManager { 

        } 
 

 
        #endregion Private Methods

        #region Private Fields
 
        private const string RefererHeader = "Referer: ";
 
        private const string BrowserOpenCommandLookupKey = "htmlfile\\shell\\open\\command"; 

        // Object to be used for locking.  Using typeof(Util) causes an FxCop 
        // violation DoNotLockOnObjectsWithWeakIdentity
        private static object _lockObj = new object();

        /// 
        ///     Critical - requires an elevation to create.
        /// 
        [SecurityCritical] 
        private static UnsafeNativeMethods.IInternetSecurityManager _secMgr ;
 
        private static SecurityMgrSite _secMgrSite ;

        #endregion Private Fields
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------------------------ 
//  Microsoft Avalon
//  Copyright (c) Microsoft Corporation, 2001
//
//  File:           appsecuritymanager.cs 
//
//  Description:    AppSecurityManager class. 
// 
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// IMPORTANT: We are creating an instance of IInternetSecurityManager here. This 
// is currently also done in the CustomCredentialPolicy at the Core level. Any
// modification to either of these classes--especially concerning MapUrlToZone--
// should be considered for both classes.
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
//
//  History:        04-24-02 - [....] - created 
//----------------------------------------------------------------------------- 

using System; 
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.Security; 
using System.Security.Permissions;
using Microsoft.Win32; 
using System.IO.Packaging; 
using System.Windows;
using System.Windows.Interop; 
using MS.Internal.Utility;
using MS.Win32;
using System.Runtime.InteropServices;
using MS.Internal.Documents.Application; 

using SecurityHelper=MS.Internal.SecurityHelper; 
 
namespace MS.Internal.AppModel
{ 

    enum LaunchResult
    {
        Launched, 
        NotLaunched,
        NotLaunchedDueToPrompt 
    }; 

    internal static class AppSecurityManager 
    {
        #region Internal Methods

 
        ///
        ///     Safely launch the browser if you can. 
        ///     If you can't demand unmanaged code permisison. 
        ///
        ///     originatingUri = the current uri 
        ///     destinationUri = the uri you are going to.
        ///
        ///
        /// Critical - gets access to critical resource (uri), calls critical code (launch browser) 
        /// TreatAsSafe because
        ///                     we consider navigates to http or http to the top-level browser as safe. 
        ///                     we consider navigates to mailto as safe. 
        ///                     for all other cases - we demand Unmanaged Code Permission
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        internal static void SafeLaunchBrowserDemandWhenUnsafe( Uri originatingUri, Uri destinationUri, bool fIsTopLevel )
        {
            LaunchResult launched = LaunchResult.NotLaunched; 

            launched = SafeLaunchBrowserOnlyIfPossible( originatingUri, destinationUri, fIsTopLevel ) ; 
            if ( launched == LaunchResult.NotLaunched ) 
            {
                SecurityHelper.DemandUnmanagedCode(); 
                UnsafeLaunchBrowser( destinationUri );
            }
        }
 
        ///
        /// Safely launch the browser if it's possible to do so in partial trust 
        /// Returns enum indicating whether we safely launched ( or at least think we did). 
        ///
        ///     This function is appropriate for use when we launch the browser from partial trust 
        ///     ( as it doesn't perform demands for the "unsafe" cases )
        ///
        internal static LaunchResult SafeLaunchBrowserOnlyIfPossible(Uri originatingUri, Uri destinationUri, bool fIsTopLevel)
        { 
            return SafeLaunchBrowserOnlyIfPossible(originatingUri, destinationUri, null, fIsTopLevel);
        } 
 
        ///
        /// Safely launch the browser if it's possible to do so in partial trust 
        /// Returns enum indicating whether we safely launched ( or at least think we did).
        /// Html target names can be passed in with this.
        ///     This function is appropriate for use when we launch the browser from partial trust
        ///     ( as it doesn't perform demands for the "unsafe" cases ) 
        ///
        /// 
        /// Critical - gets access to critical resource (uri), calls critical code (launch browser) 
        ///
        /// TreatAsSafe because 
        ///                     we consider navigates to http or http to the top-level browser as safe.
        ///                     we consider navigates to mailto as safe.
        ///
        ///                     for all other cases - we don't launch the browser - and return a result 
        ///                     indicating that we didn't launch.
        /// 
        [SecurityCritical, SecurityTreatAsSafe] 
        internal static LaunchResult SafeLaunchBrowserOnlyIfPossible(Uri originatingUri, Uri destinationUri, string targetName, bool fIsTopLevel )
        { 
            LaunchResult launched = LaunchResult.NotLaunched ;

            bool isKnownScheme =  (Object.ReferenceEquals(destinationUri.Scheme, Uri.UriSchemeHttp))  ||
                                   (Object.ReferenceEquals(destinationUri.Scheme, Uri.UriSchemeHttps)) || 
                                   destinationUri.IsFile;
 
            bool fIsMailTo = String.Compare(destinationUri.Scheme, Uri.UriSchemeMailto, StringComparison.OrdinalIgnoreCase) == 0 ; 

            // We elevate to navigate the browser iff: 
            //  We are user initiated AND
            //      Scheme == http/https & topLevel OR scheme == mailto.
            //
            // For all other cases ( evil protocols etc). 
            // We will demand.
            // 
            // The check of IsInitialViewerNavigation is necessary because viewer applications will probably 
            // need to call Navigate on the URI they receive, but we want them to be able to do it in partial trust.
            if ((!BrowserInteropHelper.IsInitialViewerNavigation && 
                MS.Internal.PresentationFramework.SecurityHelper.CallerHasUserInitiatedNavigationPermission()) &&
                  ((fIsTopLevel && isKnownScheme) || fIsMailTo) )
            {
                if (isKnownScheme) 
                {
                    IBrowserCallbackServices ibcs = ( Application.Current != null ) ? Application.Current.BrowserCallbackServices : null ; 
                    if (ibcs != null) 
                    {
                        launched = CanNavigateToUrlWithZoneCheck(originatingUri , destinationUri); 
                        if ( launched == LaunchResult.Launched )
                        {
                            // resetting launched to NotLaunched here; if the assert succeeds
                            // and ibcs.DelegateNavigation does not throw then we will set it to Launched. 
                            launched = LaunchResult.NotLaunched;
                            // Browser app. 
                            // 

                            ibcs.DelegateNavigation( BindUriHelper.UriToString( destinationUri ), targetName, GetHeaders(destinationUri)); 

                            launched = LaunchResult.Launched ;
                        }
                    } 
                }
                else if ( fIsMailTo ) // unnecessary if - but being paranoid. 
                { 
                    // Shell-Exec the browser to the mailto url.
                    // assumed safe - because we're only allowing this for mailto urls. 
                    //

                    UnsafeNativeMethods.ShellExecute( new HandleRef( null, IntPtr.Zero) , /*hwnd*/
                                                      null, /*operation*/ 
                                                      BindUriHelper.UriToString( destinationUri ), /*file*/
                                                      null, /*parameters*/ 
                                                      null, /*directory*/ 
                                                      0 ); /*nShowCmd*/
                    launched = LaunchResult.Launched ; 
                }
            }

            return launched ; 
        }
 
        // This invokes the browser unsafely. 
        // Whoever is calling this function should do the right demands.
        ///  
        /// Critical - gets access to critical resource (uri and browsercallback services), calls critical code (launch browser)
        /// 
        [SecurityCritical]
        internal static void UnsafeLaunchBrowser(Uri uri, string targetFrame = null) 
        {
            // This'll likely go into SafeLaunchBrowser() function. 
            if (Application.Current != null && Application.Current.CheckAccess()) 
            {
                IBrowserCallbackServices ibcs = Application.Current.BrowserCallbackServices; 
                if (ibcs != null)
                {
                    // Browser app.
                    // 

                    ibcs.DelegateNavigation(BindUriHelper.UriToString(uri), targetFrame, GetHeaders(uri)); 
                    return; 
                }
            } 

            ShellExecuteDefaultBrowser(uri);
        }
 
        /// 
        /// Opens the default browser for the passed in Uri. 
        ///  
        /// 
        /// Critical - calls critical code (ShellExecuteEx) 
        /// 
        [SecurityCritical]
        internal static void ShellExecuteDefaultBrowser(Uri uri)
        { 
            UnsafeNativeMethods.ShellExecuteInfo sei = new UnsafeNativeMethods.ShellExecuteInfo();
            sei.cbSize = Marshal.SizeOf(sei); 
            sei.fMask = UnsafeNativeMethods.ShellExecuteFlags.SEE_MASK_FLAG_DDEWAIT; 
            /*
            There is a bug on Windows Vista (with IE 7): ShellExecute via SEE_MASK_CLASSNAME fails for an 
            http[s]:// URL. It works fine for file://. The cause appears to be that the DDE command template
            defined in HKCR\IE.AssocFile.HTM\shell\opennew\ddeexec is used: [file://%1",-1,,,,,]. On XP, the
            the key used is (supposedly) HKCR\htmlfile\shell\opennew\ddeexec, and its value is ["%1",,-1,0,,,,].
            The workaround here is to add the SEE_MASK_CLASSNAME flag only for non-HTTP URLs. For HTTP, 
            "plain" ShellExecute just works, incl. with Firefox/Netscape as the default browser.
            */ 
            if (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps) 
            {
                sei.fMask |= UnsafeNativeMethods.ShellExecuteFlags.SEE_MASK_CLASSNAME; 
                sei.lpClass = ".htm"; // The default browser is looked up by this.
            }
            sei.lpFile = uri.ToString(); // It's safe to use Uri.ToString since there's an inheritance demand on it that prevents spoofing by subclasses.
            if (!UnsafeNativeMethods.ShellExecuteEx(sei)) 
                throw new InvalidOperationException(SR.Get(SRID.FailToLaunchDefaultBrowser),
                    new System.ComponentModel.Win32Exception(/*uses the last Win32 error*/)); 
        } 

        #endregion Internal Methods 

        #region Private Methods

        ///  
        /// Returns the HTTP "Referer" header.
        ///  
        /// returns a string containing one or more HTTP headers separated by \r\n; the string must also be terminated with a \r\n 
        private static string GetHeaders(Uri destinationUri)
        { 
            string referer = BindUriHelper.GetReferer(destinationUri);

            if (!String.IsNullOrEmpty(referer))
            { 
                // The headers we pass in to IWebBrowser2.Navigate must
                // be terminated with a \r\n because the browser then 
                // concatenates its own headers on to the end of that string. 
                referer = RefererHeader + referer + "\r\n";
            } 

            return referer;
        }
 
        //
        // Functionally equivalent copy of Trident's CanNavigateToUrlWithZoneCheck function 
        //  Checks to see whether a navigation is considered a zone elevation. 
        //  Once a zone elevation is identified - calls into urlmon to check settings.
        // 
        ///
        ///     Critical - performs elevations to call IsFeatureEnabled; call critical method MUTZ
        ///
        ///     TreatAsSafe - information disclosed is whether a navigation is "safe" or not. 
        ///                   considered ok to give out. you will be able to get this anyway by trapping exceptions
        ///                   or seeing whether a navigation succeeded/failed. 
        /// 
        [SecurityCritical, SecurityTreatAsSafe ]
        private static LaunchResult CanNavigateToUrlWithZoneCheck( Uri originatingUri, Uri destinationUri ) 
        {
            LaunchResult launchResult = LaunchResult.NotLaunched ; // fail securely - assume this is the default.
            int targetZone = NativeMethods.URLZONE_LOCAL_MACHINE ; // fail securely this is the most priveleged zone
            int sourceZone = NativeMethods.URLZONE_INTERNET ; // fail securely this is the least priveleged zone. 
            bool fEnabled = true;
 
            EnsureSecurityManager(); 

            // is this feature enabled ? 
            fEnabled  =  UnsafeNativeMethods.CoInternetIsFeatureEnabled(
                                        NativeMethods.FEATURE_ZONE_ELEVATION,
                                        NativeMethods.GET_FEATURE_FROM_PROCESS) != NativeMethods.S_FALSE ;
 
            targetZone = MapUrlToZone(destinationUri);
 
            // Get source zone. 

            // Initialize sourceUri to null so that source zone defaults to the least privileged zone. 
            Uri sourceUri = null;

            // If the MimeType is not a container, attempt to find sourceUri.
            // sourceUri should be null for Container cases, since it always assumes 
            // the least privileged zone (InternetZone).
            if (Application.Current.MimeType != MimeType.Document) 
            { 
                sourceUri = BrowserInteropHelper.Source;
            } 
            else if (destinationUri.IsFile &&
                System.IO.Path.GetExtension(destinationUri.LocalPath)
                    .Equals(DocumentStream.XpsFileExtension, StringComparison.OrdinalIgnoreCase))
            { 
                // In this case we know the following:
                //  1) We are currently a Container 
                //  2) The destination is a File and another Container 

                // In this case we want to treat the destination as internet too so Container 
                // can navigate to other Containers by passing zone checks
                targetZone = NativeMethods.URLZONE_INTERNET;
            }
 
            if ( sourceUri != null )
            { 
                sourceZone = MapUrlToZone(sourceUri); 
            }
            else 
            {
                // 2 potential ways to get here.
                //      a) We aren't a fusion hosted app. Assume full-trust.
                //      b) Some bug in hosting caused source Uri to be null. 
                //
                // For a - we will say there is no cross-domain check. 
                //     b - we'll assume InternetZone, and use Source. 

                bool fTrusted = SecurityHelper.CheckUnmanagedCodePermission(); 

                if ( fTrusted )
                {
                    return LaunchResult.Launched; 
                }
                else 
                { 
                    //
                    //  If we didn't get a SourceUri, we'll assume internet zone. 
                    //  And use Source for the uri of origin.
                    //
                    //  This isn't quite right - but the sourceUri is only used to show a message to the user.
                    //  Worse case is confusing user experience. ( this uri is not used in the elevation determination). 
                    //
 
                    sourceZone = NativeMethods.URLZONE_INTERNET ; 
                    sourceUri = originatingUri ;
                } 
            }

            // 
            // ------------------------------ 
            // Check if there is a zone elevation.
            // Custom zones would have a higher value that URLZONE_UNTRUSTED, so this solution isn't quite complete. 
            // However, we don't know of any product actively using custom zones, so rolling this out. 
            // INTRANET and TRUSTED are treated as equals.
            // ------------------------------ 
            // 

            //
            // Note the negative logic - it first sees to see something is *not* a zone elevation. 
            //
            if ( 
                    // Even if feature is disabled. 
                    // We still block navigation to local machine.
                    // IF source zone is internet or restricted. 

                    (!fEnabled &&
                    ((sourceZone != NativeMethods.URLZONE_INTERNET &&
                       sourceZone != NativeMethods.URLZONE_UNTRUSTED) || 
                       targetZone != NativeMethods.URLZONE_LOCAL_MACHINE)) ||
 
                    // If feature is enabled 
                    // It's not a zone elevation if
                    //     the zones are equal OR 
                    //        the zones are both less than restricted and
                    //            the sourceZone is more trusted than Target OR
                    //            sourceZone and TargetZone are both Intranet or Trusted
                    // 
                    // per aganjam - Intranet and Trusted are treated as equivalent
                    // as it was a common scenario for IE. ( website on intranet points to trusted site). 
                    // 

                    (fEnabled && 
                    (
                        sourceZone == targetZone ||
                        (
                            sourceZone <= NativeMethods.URLZONE_UNTRUSTED && 
                            targetZone <= NativeMethods.URLZONE_UNTRUSTED &&
                            ( 
                                sourceZone < targetZone || 
                                (
                                    (sourceZone == NativeMethods.URLZONE_TRUSTED || sourceZone == NativeMethods.URLZONE_INTRANET) && 
                                    (targetZone == NativeMethods.URLZONE_TRUSTED || targetZone == NativeMethods.URLZONE_INTRANET)
                                )
                            )
                        ) 
                        )))
            { 
                // There is no zone elevation. You can launch away ! 
                return LaunchResult.Launched ;
            } 

            launchResult = CheckBlockNavigation( sourceUri ,
                                                  destinationUri,
                                                  fEnabled ) ; 

            return launchResult ; 
        } 

        /// 
        ///     Called when we suspect there is a zone elevation.
        ///     Calls the Urlmon IsFeatureZoneElevationEnabled which may pop UI based on settings.
        /// functionally equivalent to the BlockNavigation: label in Trident's CanNavigateToUrlWithZoneCheck
        /// 
        ///
        ///     Critical - calls a function that has a SUC on it. ( CoIntenrnetIsFeatureZoneElevationEnabled) 
        /// 
        [SecurityCritical]
        private static LaunchResult CheckBlockNavigation(Uri originatingUri, Uri destinationUri, bool fEnabled ) 
        {

            if (fEnabled)
            { 
                if( UnsafeNativeMethods.CoInternetIsFeatureZoneElevationEnabled(
                            BindUriHelper.UriToString( originatingUri ) , 
                            BindUriHelper.UriToString( destinationUri ) , 
                            _secMgr,
                            NativeMethods.GET_FEATURE_FROM_PROCESS) == NativeMethods.S_FALSE) 
                {
                    return LaunchResult.Launched;
                }
                else 
                {
                    if ( IsZoneElevationSettingPrompt( destinationUri ) ) 
                    { 
                        // url action is query, and we got a "no" answer back.
                        // we can assume user responded No. 
                        return LaunchResult.NotLaunchedDueToPrompt ;
                    }
                    else
                        return LaunchResult.NotLaunched ; 
                }
            } 
            else 
            {
                return LaunchResult.Launched ; 
            }
        }

        // Is ZoneElevation setting set to prompt ? 
        ///
        ///     Critical - elevates to call ProcessUrlAction. 
        ///     TreatAsSafe - information return indicates whether we will prompt for the current zone. 
        ///                   considered ok to expose.
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        private static bool IsZoneElevationSettingPrompt( Uri target )
        {
            Invariant.Assert(_secMgr != null ); 

            // Was this due to a prompt ? 
 
            int policy = NativeMethods.URLPOLICY_DISALLOW ;
 
            unsafe
            {
                String targetString = BindUriHelper.UriToString( target ) ;
                new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); // BlessedAssert: 
                try
                { 
                    _secMgr.ProcessUrlAction( targetString, 
                                              NativeMethods.URLACTION_FEATURE_ZONE_ELEVATION ,
                                              (byte*) & policy , 
                                              Marshal.SizeOf( typeof (int) ),
                                              null,
                                              0,
                                              NativeMethods.PUAF_NOUI, 
                                              0 );
                } 
                finally 
                {
                    CodeAccessPermission.RevertAssert(); 
                }
            }

            return ( policy == NativeMethods.URLPOLICY_QUERY ) ; 
        }
 
        /// 
        /// Critical - elevates to call unmanaged code to set the security site.
        ///     The SecurityManager is a critical resource. 
        /// Safe: The Security Manager is used only within this class (not exposed). Just creating it has
        ///     no observable side effects.
        ///
        [SecurityCritical, SecurityTreatAsSafe] 
        private static void EnsureSecurityManager()
        { 
            // IMPORTANT: See comments in header r.e. IInternetSecurityManager 

            if( _secMgr == null ) 
            {
                lock( _lockObj )
                {
                    if ( _secMgr == null ) // null check again - now that we're in the lock. 
                    {
                          _secMgr = (UnsafeNativeMethods.IInternetSecurityManager) new InternetSecurityManager(); 
 
                          //
                         // Set the Security Manager Site. 
                         // This enables any dialogs popped to be modal to our window.
                         //
                        _secMgrSite = new SecurityMgrSite();
                        new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); // BlessedAssert: 
                        try
                        { 
                             _secMgr.SetSecuritySite( (NativeMethods.IInternetSecurityMgrSite) _secMgrSite ) ; 
                        }
                        finally 
                        {
                            CodeAccessPermission.RevertAssert();
                        }
                    } 
                }
            } 
        } 

 
        ///
        /// Critical - elevates to call SetSecuritySite.
        /// TreatAsSafe - clearing the security site is considered safe.
        ///               worse that can happen is any urlmon prompts will be non-modal. 
        ///
 
        [SecurityCritical, SecurityTreatAsSafe ] 
        internal static void ClearSecurityManager()
        { 
            if (_secMgr != null )
            {
                new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();  // BlessedAssert:
                try 
                {
                    lock ( _lockObj ) 
                    { 
                        if ( _secMgr != null )
                        { 
                            _secMgr.SetSecuritySite( null ) ;
                            _secMgrSite = null ;
                            _secMgr = null ;
                        } 
                    }
                } 
                finally 
                {
                    CodeAccessPermission.RevertAssert(); 
                }


            } 
        }
 
        /// 
        /// Critical - Calls the COM method. A URL's security zone is not a big secret, and in most cases it
        ///     can be inferred by just parsing the URL, but it's still information obtained under elevation, 
        ///     and thus we shouldn't leak it without a good reason.
        ///
        [SecurityCritical]
        internal static int MapUrlToZone(Uri url) 
        {
            EnsureSecurityManager(); 
            int zone; 
            _secMgr.MapUrlToZone(BindUriHelper.UriToString(url), out zone, 0);
            return zone; 
        }

        [ComImport, ComVisible(false), Guid("7b8a2d94-0ac9-11d1-896c-00c04Fb6bfc4")]
        internal class InternetSecurityManager { 

        } 
 

 
        #endregion Private Methods

        #region Private Fields
 
        private const string RefererHeader = "Referer: ";
 
        private const string BrowserOpenCommandLookupKey = "htmlfile\\shell\\open\\command"; 

        // Object to be used for locking.  Using typeof(Util) causes an FxCop 
        // violation DoNotLockOnObjectsWithWeakIdentity
        private static object _lockObj = new object();

        /// 
        ///     Critical - requires an elevation to create.
        /// 
        [SecurityCritical] 
        private static UnsafeNativeMethods.IInternetSecurityManager _secMgr ;
 
        private static SecurityMgrSite _secMgrSite ;

        #endregion Private Fields
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK