Logging: Permission Issue

Topics: Logging Application Block
Jul 9, 2012 at 12:01 AM

I am using Microsoft CRM 2011 Online, i.e. it is being hosted be a 3rd party and I have no control over that host machine. I am developing a plugin and try to use the logging block. However, I am getting the following error:

Request for the permission of type 'System.Security.Permissions.FileIOPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=...' failed.

How can I get this to work. Btw, I am using the following code from a 3rd party:

private void SetLogfilePath(string logfilePath) {
    ConfigurationFileMap configPath = new ConfigurationFileMap();
    configPath.MachineConfigFilename = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
    Configuration entlibConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    LoggingSettings loggingSettings = (LoggingSettings)entlibConfig.GetSection(LoggingSettings.SectionName);
 
    TraceListenerData traceListenerData = loggingSettings.TraceListeners.Get("Flat File Trace Listener");
    FlatFileTraceListenerData flatFileTraceListenerData = traceListenerData as FlatFileTraceListenerData;
 
    if (!string.IsNullOrEmpty(logfilePath)) {
        flatFileTraceListenerData.FileName = logfilePath;
    }
    else {
        flatFileTraceListenerData.FileName = @"C:\Exception Log\" + DateTime.Now.ToString("yyyyMMdd") + ".log";
    }
    entlibConfig.Save();

 

Jul 10, 2012 at 6:11 AM

It would help to see the full stack trace.  Do you know what line is throwing the exception?  

It sounds like Code Access Security of the host machine is restricting access to the file system.  How are your assemblies deployed?  Is your code being run from a network share? 

If you truly have no control over the system and you cannot save the configuration then you may have to configure Enterprise Library in memory. 

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com 

Jul 10, 2012 at 7:00 AM

Thanks for the response, Randy. Here is the info about the exception:

Message: Request for the permission of type 'System.Security.Permissions.FileIOPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed.

Stack: at System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet)
   at System.Security.CodeAccessPermission.Demand()
   at System.AppDomainSetup.VerifyDir(String dir, Boolean normalize)
   at Logging.ExceptionLog.SetLogfilePath(String logfilePath)
   at Logging.ExceptionLog.Write(Exception ex, String logfilePath)
   at EventAttendanceEmailPlugin.EventAttendanceEmailPlugin.Execute(IServiceProvider serviceProvider)

My code is in a dll which I register through a tool provided my MS. That tool loads the dll and deployes it on the server that hosts the CRM application. There are to options to chose for the isolation mode, i.e. Sandbox and None. For the online version of Dynamics CRM I must select Sandbox. The location of the assembly is Database. There is also the option of Disk and GAC but again the tool fails for those locations when using CRM Online. What options are there to use the logging block for this type of scenario?

 

Jul 11, 2012 at 4:35 AM

There is no real away to circumvent the code access security except to change the trust level/policy or to avoid the scenario that causes the exception.  The issue appears to be that you are not allowed to save the configuration.

So there are 2 options: try to achieve the settings/deployment so that there is enough privilege to save the configuration or to change the approach.  For the former scenario, you would need to work with the CRM Application/host.  For the latter, I would recommend not relying on saving the configuration to disk and do it in memory.

An example of the latter approach with your code could be:

private static void SetLogfilePath(string logfilePath)
{
    ConfigurationFileMap configPath = new ConfigurationFileMap();
    configPath.MachineConfigFilename = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
    Configuration entlibConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    LoggingSettings loggingSettings = (LoggingSettings)entlibConfig.GetSection(LoggingSettings.SectionName);

    TraceListenerData traceListenerData = loggingSettings.TraceListeners.Get("Flat File Trace Listener");
    FlatFileTraceListenerData flatFileTraceListenerData = traceListenerData as FlatFileTraceListenerData;

    if (!string.IsNullOrEmpty(logfilePath))
    {
        flatFileTraceListenerData.FileName = logfilePath;
    }
    else
    {
        flatFileTraceListenerData.FileName = @"C:\Exception Log\" + DateTime.Now.ToString("yyyyMMdd") + ".log";
    }

    var configSource = new DictionaryConfigurationSource();
    configSource.Add(LoggingSettings.SectionName, loggingSettings);
    EnterpriseLibraryContainer.Current
        = EnterpriseLibraryContainer.CreateDefaultContainer(configSource);
} 

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com 

Jul 11, 2012 at 6:37 AM

Thanks Randy. I believe I will have to go with the second option. It looks like your function replaces the entlibConfig.Save() with the last 3 lines in your code. Frankly, I don't quite understand what those lines are doing. In any case, after some more debugging I realized the exception is thrown at a different place than I expected. It happens at the line

configPath.MachineConfigFilename = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;

The offending code is the AppDomain.CurrentDomain....

I guess it tries to read the infromation from a config file but I don't have access to the file. It actually makes me wonder if it exists in the first place. Is there a way to get that information by other means?

 Btw, I am calling

ExceptionLog log = new ExceptionLog();
log.Write(new Exception("My Exception"));

 

Jul 12, 2012 at 5:06 AM

Actually, now that you mention it, I don't think you need those 2 lines at all:

    ConfigurationFileMap configPath = new ConfigurationFileMap();
    configPath.MachineConfigFilename = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;

The configPath is not used after those lines and you shouldn't have to set the machine.config location.  If you remove those 2 lines and add back in the call to .Save() does it work?

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com 


Jul 12, 2012 at 6:04 AM

Thanks. I have reduced the code to

Configuration entlibConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
LoggingSettings loggingSettings = (LoggingSettings)entlibConfig.GetSection(LoggingSettings.SectionName);
TraceListenerData traceListenerData = loggingSettings.TraceListeners.Get("Flat File Trace Listener");
FlatFileTraceListenerData flatFileTraceListenerData = traceListenerData as FlatFileTraceListenerData;
if (!string.IsNullOrEmpty(logfilePath)) {
    flatFileTraceListenerData.FileName = logfilePath;
} else {
    flatFileTraceListenerData.FileName = @"C:\Exception Log\" + DateTime.Now.ToString("yyyyMMdd") + ".log";
}
entlibConfig.Save();

Unfortunately it fails right at the first line, i.e. OpenExeConfiguration. The exception I get is below. Note that I have the logging part in a separate dll. However, I do an ILMerge of this dell so I think that is not the issue, i.e. this is probably an actual access problem and not that the config file is missing.

Message:
An error occurred loading a configuration file: Request for the permission of type 'System.Security.Permissions.FileIOPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=...' failed. (machine.config)

Inner Exception:
System.Security.SecurityException: Request for the permission of type 'System.Security.Permissions.FileIOPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=...' failed.
   at System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet)
   at System.Security.CodeAccessPermission.Demand()
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
   at System.Configuration.Internal.InternalConfigHost.System.Configuration.Internal.IInternalConfigHost.OpenStreamForRead(String streamName, Boolean assertPermissions)
   at System.Configuration.BaseConfigurationRecord.InitConfigFromFile()
The action that failed was:
Demand
The type of the first permission that failed was:
System.Security.Permissions.FileIOPermission
The Zone of the assembly that failed was:
MyComputer

Stack Trace:
   at System.Configuration.ConfigurationSchemaErrors.ThrowIfErrors(Boolean ignoreLocal)
   at System.Configuration.BaseConfigurationRecord.ThrowIfParseErrors(ConfigurationSchemaErrors schemaErrors)
   at System.Configuration.Configuration..ctor(String locationSubPath, Type typeConfigHost, Object[] hostInitConfigurationParams)
   at System.Configuration.ClientConfigurationHost.OpenExeConfiguration(ConfigurationFileMap fileMap, Boolean isMachine, ConfigurationUserLevel userLevel, String exePath)
   at Logging.ExceptionLog.SetLogfilePath(String logfilePath)
, inner:
, stack:    at Logging.ExceptionLog.SetLogfilePath(String logfilePath)
   at Logging.ExceptionLog.Write(Exception ex, String logfilePath)
   at EventAttendanceEmailPlugin.EventAttendanceEmailPlugin.Execute(IServiceProvider serviceProvider)

Jul 12, 2012 at 7:02 AM

So it looks like you are trying to open the config file for read but the code access security is demanding FileIOPermission for your assembly that is zoned as MyComputer and that is failing.  I'm curious if you can read any file from disk (e.g. File.ReadAllText)?  

The issue seems to be with the way the assembly is packaged and deployed and/or the policies set on the server.  If you can't read from disk then there isn't much hope for the current approach.  In that case, you could try to resolve the security issue or you could switch to programmatic configuration using the Fluent Interface (or switch to the fluent interface while you try to resolve the security issue).

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com 

Jul 17, 2012 at 5:32 AM

The plugins are running in a sandbox and there are many restrictions. I have no read access rights to files. So I guess my only option then is the fluent interface. Is there some utility that loads an embedded app.config file and executes the appropriate code using the fluent interface? If not is there some easy way to manually map what is in the config file to build the command builder.ConfigureLogging()....?

Btw, since I don't have access to files or event logs I will have to log into the database. Checking out various resources including the one you mentioned I came up with the code below. However, it does not have a definition for ".Database..." and fails to compile. Also somewhere I guess I need to add the connection string.

private void Test(Exception ex)
{
    ConfigurationSourceBuilder builder = new ConfigurationSourceBuilder();
    DictionaryConfigurationSource configSource;

    //Do I need this?
    builder.ConfigureInstrumentation()
            .ForApplicationInstance("MyApp")
            .EnableLogging()
            .EnablePerformanceCounters();

    //Configre logging block
    builder.ConfigureLogging()
        .WithOptions
            .DoNotRevertImpersonation()
        .LogToCategoryNamed("DBLogger")
            .SendTo
                .Database("Formatted Database TraceListener")
                .UseDatabase("MyDatabase")
                .FormatWith(new FormatterBuilder().TextFormatterNamed("Text Formatter"));

    configSource = new DictionaryConfigurationSource();
    builder.UpdateConfigurationWithReplace(configSource);
    EnterpriseLibraryContainer.Current = EnterpriseLibraryContainer.CreateDefaultContainer(configSource);

    LogEntry entry = new LogEntry()
    {
        Message = ex.Message,
        Severity = TraceEventType.Critical
    };

    LogWriter logWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();
    logWriter.Write(entry);
}

 

 

Jul 18, 2012 at 1:11 AM

There is no tool for mapping configuration to the fluent interface.  

You don't need to explicitly call ConfigureInstrumentation.

For database logging you need to add a reference to the Microsoft.Practices.EnterpriseLibrary.Logging.Database.dll as well as to the Data Access Block.

Your configuration/code would look something like:

    ConfigurationSourceBuilder builder = new ConfigurationSourceBuilder();
    DictionaryConfigurationSource configSource;

    //Configre logging block
    builder.ConfigureLogging()
        .WithOptions
            .DoNotRevertImpersonation()
        .LogToCategoryNamed("DBLogger")
            .SendTo
                .Database("Formatted Database TraceListener")
                .UseDatabase("MyDatabase")
                .FormatWith(new FormatterBuilder().TextFormatterNamed("Text Formatter"));

    builder.ConfigureData()
        .ForDatabaseNamed("MyDatabase")
            .AsDefault()
            .ThatIs.ASqlDatabase()
                .WithConnectionString("Database=Logging;Integrated Security=SSPI");

    configSource = new DictionaryConfigurationSource();
    builder.UpdateConfigurationWithReplace(configSource);
    EnterpriseLibraryContainer.Current = EnterpriseLibraryContainer.CreateDefaultContainer(configSource);

    LogEntry entry = new LogEntry()
    {
        Message = "ex.Message",
        Severity = TraceEventType.Critical
    };

    LogWriter logWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();
    logWriter.Write(entry);

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com