InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

How to grant access to all mailboxes on a mailbox store to a special account.

Description

To grant an account access to all mailboxes on a mailbox store, the easiest way it to grant the "Receive as" and "Send as" permissions on the mailbox store. To do this, navigate to the mailbox store in the Exchange Systems manager:


The mailbox store in the Exchange Systems Manager

Next, select "Properties" on the context menu of the mailbox store and select the "Security tab".

If you want to grant the access to all mailboxes to a non-administrative account, you can simply add that account to the list and grant the "Send As" and "Receive As" permission.

To grant those permissions to an administrative account, you must perform a few additional steps, since members of the Domain Administrators have a deny on these permission


The security properties of the mailbox store

This denial is placed at the organizational level. This can be examined with ADSIEdit (adsiedit.msc):


ADSI edit displaying the security settings for the Exchange Organization

You can see, that the Domain Administrators have both, an allow as well as a deny permission. This is propagated down to the mailbox store, and prevents administrative accounts from accessing other users mailboxes. Now, you can remove these deny permission, but this is not advisable - they are there for a reason. You would end up with far too many people having these rights.

A better approach is to give only one specific account these rights. If this account is also in the Domain Administrators group and already displayed in the security tab, click "Advanced" and then "Add". Select the account the grant the "Receive As" and "Send as" permissions.

The account will be granted the permissions, because an explicit allow on a lower level overrides an inherited deny.


Posted by Henning Krause on Thursday, April 13, 2006 12:00 AM, last modified on Thursday, April 13, 2006 12:00 PM
Permalink | Post RSSRSS comment feed

Building managed event sinks

Update

  • 2008-04-11
    • Added information for EventSinks on Windows 2000
  • 2007-09-13
    • Revised the last section and clarified necessary permissions to run the event sink
    • Added links to related articles

More Information

On MSDN , there is an article describing describes the necessary steps to create an event sink with managed code (Building Managed Event Sink DLLs), and another one (Managing Event Registrations) explains how to register the eventsink on the target server. Unfortunately, that article does not explain how to deploy the event sink into production. The part where the COM+ application is created is simply missing. Additionally, the example misses one attribute that should be added to the assemblyinfo.cs (or assemblyinfo.vb).

The necessary attributes

Apart from the AssemblyKeyName or AssemblyKeyFileAttribute, you should add these attributes to the assemblyinfo.* file:

    1 [assembly: ApplicationActivation(ActivationOption.Server)]

    2 [assembly: ApplicationName("Title of COM+ Application")]

    3 [assembly: ApplicationAccessControl(false ,AccessChecksLevel = AccessChecksLevelOption.Application)]

Be sure to insert the correct name of the COM+ application in line 2.

Installing the COM+ application on a production server

Once you've copied all the assemblies into a folder on the destination server, you can use the Regvcs tool to register the eventsink assembly and create the COM+ application (Note: The above link points to the .NET 2.0 documentation. But the tool is also available for .NET 1.x). 

Just call

    1 regsvcs -fc managedeventsink.dll

The only thing left to do is to set the process identity for the newly created COM+ application. To do this, open the Component Services MMC (Under administrative tools) and navigate to your COM+ application. Open the properties of the application and select the identity tab. This looks like this:


Here you should select "This user" and enter a user account which has access to the folder to which the event sink will be bound. If you are writing a global event sink which is bound to a mailbox store, and want to access items outside of the scope from the thread on which the OnSyncSave method is called, that account must have permissions to access all mailboxes on that store. See How to grant access to all mailboxes on a mailbox store to a special account for more information on how to do this.

The event sink can now be registered on the Exchange server as described in the second article mentioned at the beginning of this article (See this article if you want to install the sink on Exchange 2007 64bit)

If you want to use an application settings file, you shoud read this article.

Considerations for Windows 2000

If you need to register your event sink on Windows 2000 you will run in a few problems because the COM+ version available. There are two main issues on this platform:

  • Loading of dependent assemblies - Windows 2000 will look for dependent assemblies of the eventsink in the working directory - which is %SystemRoot%\system32. To get around this issue, install all assemblies in the Global Assembly Cache.
  • Accessing configuration data - On Windows 2003 you can access an application.config by creating a manifest file and setting some COM+ options (See How to use applications settings from an event sink). Unfortunately, the required options are not available on Windows 2000. You'll have to put your configuration settings into the file %SystemRoot%\System32\dllhost.exe.config. The file will most likely not exists, so you'll have to create it.

Posted by Henning Krause on Thursday, April 13, 2006 12:00 AM, last modified on Friday, April 11, 2008 12:00 PM
Permalink | Post RSSRSS comment feed

Iterating through all mailboxes in an Exchange 2000/2003 organization

Description [Updated]

Updates

  • 2008-07-23: Corrected intra-site links.

One question that came up lately in the newsgroups is: How can I find messages containing certain keywords in the mailboxes of all users in my organization?

This scenario is not directly supported by Exchange. All one can do is to search each mailbox individually.

You must follow these steps to do the search over all mailboxes:

  1. Enumerate the users which have a mailbox. Essentially, these are the users appearing on the global address list. See How to get the Global Address List programatically for more information on how to do this.
  2. Build the mailbox url which can be used to access the mailbox via WebDAV or ExOleDB. See Get the WebDAV url for an Exchange 2000/2003 mailbox on how to do this. If you are using the ExOleDB provider or want to use the administrative virtual root instead, see the remarks section for more information.
  3. Once you have the url for the mailbox you can start accessing it. If you must support different languages, see Getting Well-Known Mailbox Folder URLs on MSDN to get the url of the default folders.
  4. If you are using WebDAV and have Form-based-authentication enabled on your server, you must do a manual logon to the mailbox. See Access the Exchange store via WebDAV with Form-Based-Authentication turned on.

Remarks

Permissions

Depending on how you want to access the mailboxes, you need different permissions:

  • If you are using the normal urls (e.g. http://myserver/exchange/username), you need access permissions to all mailboxes on the MAPI level. See HOWTO: Grant access to all mailboxes on a mailbox store to a special account on how to do this. If you have more than one mailbox store, you should grant the necessary permissions on each mailbox store. To simplify this process, you could also grant the "Send as" and "Receive as" permission on the Administrative Groups container via ADSIEdit.msc instead of each mailbox store.
  • You can also use the administrative virtual root. This method is used by the Exchange Systems manager, and it is available via WebDAV and ExOleDB. The normal MAPI permissions are completely ignored when using this method, but an administrative account is required to use this method (See Working with Store Permissions in Microsoft Exchange 2000 and 2003 on Technet for more information on this topic).

Mailbox urls

  • If you are using WebDAV to access the store, you can simply build the mailbox url based on the article Get the WebDAV url for an Exchange 2000/2003 mailbox. To use the administrative root instead, modify the url from http://myserver/exchange/mailboxname to http://myserver/exadmin/admin/<dsndomainname>/mbx/<mailboxname>. You must replace the <dnsdomainname> with the primary smtp domain name of your organization.
  • If you are using ExOleDB, you must modify the address from http://myserver/exchange/mailboxname to file://./backofficestorage/<dnsdomainname>/mbx/<mailboxname>. To use the administrative virtual root, change this url to file://./backofficestorage/admin/<dnsdomainname>/mbx/<mailboxname>.

Posted by Henning Krause on Thursday, April 13, 2006 12:00 AM, last modified on Wednesday, July 23, 2008 10:59 PM
Permalink | Post RSSRSS comment feed

Managing InfoPath blocked attachments

Description

Outlook refuses to open certain file types received in an email. There are two level: Level 1 files cannot be accessed by the user, and level 2 files must be saved to disk before they can be opened.

InfoPath has a similar restriction: File with specific extensions cannot be attached to a formular. In fact, the InfoPath model stacks on top of the Outlook restriction list. To unblock an extension for InfoPath formulars, it must be unblocked both, in Outlook and InfoPath. 

Solution

To unblock an extension in InfoPath, follow these steps:

  1. Unblock the extension in Outlook (see Regain access to blocked atachments in Outlook on how to do this).
  2. Open the registry editor
  3. If you are using InfoPath 2003, navigate to the key HKEY_CURRENT_USER\Sofware\Microsoft\Office\11.0\InfoPath\Security. If you are using InfoPat 2007, navigate to the key HKEY_CURRENT_USER\Sofware\Microsoft\Office\12.0\InfoPath\Security.
  4. If it does not exist, create a string value named UnsafeFileTypesRemove.
  5. Open the values and enter a semicolon delimited list of extensions (e.g. chm;msi;exe)
  6. Close the registry editor.

The new filetypes can be added to a form the next time InfoPath is restarted.

References


Technorati:

Posted by Henning Krause on Friday, March 31, 2006 12:00 AM, last modified on Friday, March 31, 2006 12:00 PM
Permalink | Post RSSRSS comment feed

InfiniTec.Threading library

Description

In his blog Michael Entin talks about using C# iterators to simplify writing of asynchronous code. Be sure to read his article before proceeding!

If you want to do a web request using the WebRequest class, you can either do this synchronously, like this one:

    1 publicstring DoRequest(string uri)

    2 {

    3     WebRequest request = WebRequest.Create(uri);

    4 

    5     WebResponse response = request.GetResponse();

    6 

    7     using (Stream stream = response.GetResponseStream())

    8     {

    9         byte[] buffer = newbyte[4096];

   10         StringBuilder result = newStringBuilder();

   11         do

   12         {

   13             stream.Read(buffer, 0, buffer.Length);

   14 

   15             if (bytesRead > 0)

   16             {

   17                 result.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));

   18             }

   19         } while (bytesRead != 0);

   20 

   21         return result.ToString();

   22     }

   23 }

Very nice and elegant. But a waste of resources. The operation blocks the caller (and with it probably the UI). The solution to this is to perform the entire request asynchronously: Use BeginGetRequestStream, EndGetRequestStream, and the other BeginXXX and EndXXX methods. You will end up with many callback methods for each of them. At the end you'll have a very complicated set of methods, which will be hard to debug.

Michael (and other) had the ingenious idea to use C# iterators to merge these two concepts, and take the best of both worlds together. The AsyncOperation class in this library implements his approach, at least the basic principles.

Creative misuse…

The basic idea is to implement the operation (e.g. downloading a website) as an iterator method:

    1 privateIEnumerable<OperationAction> DownloadUrlInternal(string url)

    2 {

    3     WebRequest request = WebRequest.Create(Url);

    4 

    5     IAsyncResult ar = request.BeginGetResponse(null, null);

    6 

    7     ProgressUpdate<Status> update = newProgressUpdate<Status>(Status.ReceivingResponse, ar.AsyncWaitHandle);

    8     yieldreturn update;

    9 

   10     WebResponse response = request.EndGetResponse(ar);

   11 }

This method implements an iterator and returns an operation action of some sort. After a call to BeginGetResponse, the method creates a ProgressChangedEventStateBag which contains the status of the current request, as well as the Waithandle returned by the BeginGetResponse. Using the 'yield return' statement, this information is passed to the caller.

The method is not resumed until the Waithandle is signaled.

The DowndloadUrl method is not called directly by an application. Instead, a wrapper class is used, called AsyncOperation. This class ensures that the DownloadUrl method is called properly and translates those OperationActions into events like ProgressChanged, Completed, Error and Timedout.

The wrapper is written this way:

    1 publicAsyncOperation<Status, string> DownloadUrl(string url)

    2 {

    3     returnnewAsyncOperation<Status, string>(DownloadUrlInternal(url, 3000);

    4 }

This call initializes a new AsyncOperation class with a timeout of 3 seconds. The calling application can use this code to run the code:

    1 helper = reader.DownloadUrl(tbUrl.Text);

    2 

    3 helper.ProgressChanged += helper_ProgressChanged;

    4 helper.Finished += helper_Finished;

    5 helper.Error += helper_Error;

    6 

    7 helper.Run();

The entire source code, as well as a sample application can be downloaded from the bottom of this page.

The AsyncOperation are intensively used in my InfiniTec.Exchange project.

More Information

 

The AsyncOperation classes

The main classes are the AsyncOperation and those derived from AsyncOperation. The most important members are displayed in this diagram:


(click to enlarge)

Most of the logic is implemented in the AsyncOperation class. However, only the AsyncOperation<StatusType> and the AsyncOperation<StatusType, ResultType> classes can be used. The first one is best suited for operation which do not yield a result. For example copying an input stream to an output stream. An example for this is the AsyncOperations.Copy


operation, which does exactly this. During progress updates, it returns the current operation (reading from the input stream or writing to the output stream), as well as the current progress, if the source stream supports seeking.

 

The second operation type reports status updates and returns a result. A good example for this would be the download of a file using the HttpWebRequest operation.

The operation actions

An asynchronous operation can return several different operation actions. These are displayed below:


(click to enlarge)

  • The ProgrssUpdate<StatusType> action is used to report progress changes to the caller. This is also the action that drives the operation. Every BeginXXX method should be followed by a ProgressUpdate action containing the IAsyncResult.AsyncWaitHandle.
  • The abstract ProgressUpdate action is only used internally.
  • The OperationResult<ResultType> is used to report the result of the operation.
  • The NestedOperation can be used to run an asynchronous operation from another asynchronous operation. The current operation is not resumed until the nested operation has completed. The calling operation can specifiy wether progress updates are propagated to the root operation. Addionally, the calling operation can specify, wehter it wants to handle a possible timeout of the nested operation and certain exception. Unhandled exceptions and timeouts are propagated up the call chain until a handler is found or until the root operation is reached.
  • The NestedOperation<StatusType> is a combination of the NestedOperation action and the ProgressUpdate<StatusType> action. This type will first report the progress update and then execute the nested operation.

Progress updates of nested operations

The main problem of propagating the progress of nested operations to the root operation is the possible incompatible types of the status types. This is solved by marking properties of the status classes of the parent operation as a nested state. The NestedState attribute is used for this purpose. During a nested progress update, the status class of the parent operation is searched for a matching property which is marked with the NestedState attribute.

ChangeLog

Version 2.0 (05/15/2006)

This version is a major update… somewhere in the middle I lost track of the changes…. Some of the changes are:

  • New class AsyncOperations. Contains a basic Stream-to-Stream copy operation
  • Changed the name of the AsyncOperationBase class to AsyncOperation
  • Added an Update method to the ProgressUpdate class
  • The type of the Timeout property has been changed from int to TimeSpan
  • The StatusBag classes have benn renamed to OperationActions.
  • Nested operation are now better supported
  • Progress updates can now be reported from a nested operation to the root operation.
  • New Class AsyncOperation<StatusType> for operations which don't return a result
  • WaitForComletion operation added to the AsyncOperation class. Blocks the calling thread until the operation is finished.

Version 1.1 (03/31/2006)

  •  Added a WaitHandle that is signaled, when the status of the operation changes. This allows better nesting of asynchronous operations.
  • Added Status, StatusInformation, Result and Exception properties. Now the current statatus can be read from the AsyncOperation class.
  • Added Abort method to the AsyncOperation class.
  • OnFinished in OnCompleted umbenannt
  • Added NDoc documentation

Version 1.0 (03/30/2006)

  • Initial release

Downloads

InfiniTec.Threading Release.zip (28,518 Bytes)
Binaries version 2.1 with debug symbols.. Signed with the InfiniTec private key.
InfiniTec.Threading source.zip (21,563 Bytes)
Source code for the InfiniTec.Threading assembly.
InfiniTec.Threading help.zip (129,542 Bytes)
Help file containing the documentation for the assembly.

Technorati:

Posted by Henning Krause on Thursday, March 30, 2006 12:00 AM, last modified on Wednesday, July 12, 2006 1:00 PM
Permalink | Post RSSRSS comment feed

Vortrag in der .NET Usergroup in Paderborn vom 09.01.2006

Zusammenfassung

Am 09.01.2006 habe ich einen Vortrag in der .NET Usergroup Paderborn gehalten. Vorgestellt wurden einige ASP.NET Features und einige der neuen Security Features in .NET 2.0

Inhalt

Die folgenden Themen kommen in dem Vortrag vor:

  • ASP.NET Features
    • URL Rewriting
    • Eigene SitemapProvider
    • Caching
    • ObjectDatasource
    • XML Transformationen mit Extension Objekten
    • Mehrsprachigkeit
  • Security
    • SecureString
    • Sichere Passworteingabe
    • Passwörter speichern (ProtectedData, ProtectedMemory)

Downloads

Usergroup Vortrag 2006-01-09.ppt (1,433,600 Bytes)
Der Vortrag als Powerpoint Datei
Beispiele.zip (60,673 Bytes)
Die Beispiele aus dem Vortrag

Technorati:

Posted by Henning Krause on Sunday, January 15, 2006 12:00 AM, last modified on Sunday, January 15, 2006 12:00 PM
Permalink | Post RSSRSS comment feed

Sentinel Protection Dongle wird über ein Netzwerk nicht gefunden

Symptome

Wird ein Sentinel Dongle über ein Netzwerk benutzt (insbesonders Dial-Up oder VPN Verbindungen), so kann es vorkommen das der Dongle von der Client Applikation nicht nich gefunden wird, obwohl der Dongle auf dem Server eingesteckt ist.

Ursache

Der Sentinel Protection Server bindet sich nicht auf die allgemeine IP Adresse (0.0.0.0/0), sondern explizit auf jede verfügbare IP Adresse und wartet auf UDP Port 6001 auf eingehende Verbindungen.

Ändert sich eine IP Adresse oder wird eine neue Verbindung aufgebaut (z.B. eine VPN Verbindung), bekommt der Sentinel Protection Server dies nicht mit.

Lösung

Bei jeder Änderung von IP Adressen muss der Dienst "Sentinel Protection Server" (SentinelProtectionServer) neu gestartet werden:

    1 net stop SentinelProtectionServer

    2 net start SentinelProtectionServer

Alternativ kann die angehängte Software benutzt werden. Es handelt sich dabei um einen Windows Dienst, der bei Änderungen an der Netzwerkkonfiguration (neue IP-Adresse oder Aufbau einer DFÜ Verbindung) den in der Konfigurations-Datei angegebenen Dienst neustartet. Um den Sentinel Protection Server neustarten zu lassen, tragen Sie den Dienstnamen "SentinelProtectionServer" in die Konfigurationsdatei NetworkAddressMonitorService.exe.config:

    1 <?xmlversion="1.0"encoding="utf-8" ?>

    2 <configuration>

    3     <configSections>

   ��4         <sectionGroupname="applicationSettings"type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >

    5             <sectionname="InfiniTec.NetworkAddressMonitorService.Properties.Settings"type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"requirePermission="false" />

    6         </sectionGroup>

    7     </configSections>

    8     <applicationSettings>

    9         <InfiniTec.NetworkAddressMonitorService.Properties.Settings>

   10             <settingname="ServiceToRestart"serializeAs="String">

   11               <value>SentinelProtectionServer</value>

   12             </setting>

   13         </InfiniTec.NetworkAddressMonitorService.Properties.Settings>

   14     </applicationSettings>

   15 </configuration>

Downloads

NetworkAddressChangeSericeSetup.exe (441,457 Bytes)
NetworkAddressMonitorService Setup
NetworkAddress Monitor Service.zip (17,003 Bytes)
Quellcode

Posted by Henning Krause on Wednesday, January 11, 2006 12:00 AM, last modified on Tuesday, June 7, 2011 8:56 PM
Permalink | Post RSSRSS comment feed

Using Url rewriting and caching together

The url rewriting...

"Any problem in computer science can be solved with another layer of indirection."
David Wheeler (chief programmer for the EDSAC project in the early 1950s)

URL rewriting is a handy technique to produce much nicer URLs. Instead of http://www.infinitec.de/default.aspx?site=1&subpage=3 a user could use an address like http://www.infinitec.de/exchange/howtos/webdavevents.aspx. When the webserver processes the request, it will rewrite the URL to the webpage that processes the requests and displays the correct content.

In .NET 2.0, this is accomplished by implementing a IHttpModule and registering it in the web.config file.

Here is an example in C#:

    1 using System.Web;

    2 

    3 public class UrlRewriter : IHttpModule

    4 {

    5     #region IHttpModule Members

    6 

    7     public void Dispose() {}

    8 

    9     public void Init(HttpApplication context)

   10     {

   11         context.BeginRequest += context_BeginRequest;

   12     }

   13 

   14     void context_BeginRequest(object sender, EventArgs e)

   15     {

   16         HttpApplication app;

   17 

   18         app = (HttpApplication)sender;

   19 

   20         app.Context.RewritePath("test.aspx");

   21     }

   22 

   23     #endregion

   24 }

The Init method attaches the context_BeginRequest method with the BeginRequest event of the HttpApplication. This implementation redirects each request to the test.aspx in the same virtual directory of the server. In a real world application, this method must extract the necessary information from the original Url and redirect the execution to the correct page for further processing.

To get the sample working, the new module must be registered in the web.config file:

    1 <configuration>

    2   <system.web>

    3     <httpModules>

    4       <add name="UrlRewriter" type="UrlRewriter" />

    5     </httpModules>

    6   </system.web>

    7 </configuration>

You can now open a webbrowser and open any page on the website, regardless if it exists or not. The only restriction is that the extension of the requested page must be registered for the ASP.NET ISAPI filter. Regardless, which page you open, the request is now always processed by the test.aspx file.

When you take a look at the source code of a webpage from your browser, you will notice that the action attribute of the form tag now points back to the test.aspx file, instead of the original URL. A postback would thus reveal the true structure of your website and it might even break the functionality, because the test.aspx file might not know which content to display when called directly.

To solve this problem, the original request path must be restored before the test.aspx file is rendered:

    1 using System.Web.UI;

    2 

    3 public partial class test : Page

    4 {

    5     protected void Page_Load(object sender, EventArgs e)

    6     {

    7         Context.RewritePath("default.aspx");

    8     }

    9 }

For the sake of simplicity, the url is always rewritten to the default.aspx file.

...the caching...

Cashing is a great way to scale your application. Especially when your pages require intensive calculations. Caching is activated on a per-page base by using a page-directive at the top of each page:

    1 <%@ OutputCache Duration="600" VaryByCustom="sitemap" VaryByParam="none" %>

The duration attribute specifies that this page should be cached for 10 minutes (600 seconds). The VaryByParam parameter is set to "none", so any parameters (either POST or GET parameters) are ignored by the caching framework. Finally, the VaryByCustom parameter specifies, that a special method should be called by the the caching framework: The GetVaryByCustomString method, which must be defined in the global.asax file:

    1 <%@ Application Language="C#" %>

    2 

    3 <script runat="server">

    4 

    5     public override string GetVaryByCustomString(HttpContext context, string custom)

    6     {

    7 

    8     }

    9 </script>

...and the problems

Unfortunately, when the caching is used together with URL rewriting, the caching will be unreliable at best...

This is caused by the fact that the caching framework will now cache the output for the wrong url, since it has been rewritten to the test.aspx file. To correct this, the GetVaryByCustomString can be used:

    1 <%@ Application Language="C#" %>

    2 

    3 <script runat="server"> 

    4     public override string GetVaryByCustomString(HttpContext context, string custom)

    5     {

    6         System.Globalization.CultureInfo ci;

    7         string lang;

    8 

    9         if (!custom.Equals("sitemap", StringComparison.CurrentCultureIgnoreCase))

   10             return base.GetVaryByCustomString(context, custom);

   11 

   12         ci = context.Items["CurrentUICulture"] as System.Globalization.CultureInfo;

   13 

   14         lang = ci.DisplayName;

   15 

   16         if (context.Session != null && context.Session["ChosenLanguage"] != null) lang = ((System.Globalization.CultureInfo)context.Session["ChosenLanguage"]).DisplayName;

   17         return (string)HttpContext.Current.Items["OriginalPath"] + ci.DisplayName;

   18     }

   19 </script>

The above implementation returns the original request and the name of the selected language. Thus, a request is served from the ASP.NET cache if the requested path AND the language matches.

This implementation solves the problem that the wrong content ist displayed.


Posted by Henning Krause on Tuesday, December 27, 2005 12:00 AM, last modified on Tuesday, July 26, 2011 9:57 PM
Permalink | Post RSSRSS comment feed

A class library providing methods for acquiring and managing user credentials.

The QueryCredentialDialog

If you connect to a network resource, windows uses a common dialog to query for a username / password, as shown hiere:
(click to enlarge)This dialog can also be used from custom applications, by calling the CredUIPromptForCredentials. The QueryCredentialDialog is essentially a wrapper around this function. It is derived from the CommonDialog class, and as such, can be dropped on any Windows form. The entered password is returned securely in a SecureString object. As such, the password is encrypted most of the time.

Note that the dialog does not check whether the entered credential represents a correct username and password. This makes it possible, to use this dialog even if you are requesting arbitrary credentials.

The CredentialManager

Windows also has a credential manager, which is tightly integrated with the CredUIPromptForCredentials method. If you choose to save the credential you enter into the dialog, this credential is stored in the Windows credential manager. Unfortunately, this is true for an unlimited amount of time, so the entered credential do not expire over time.

The CredentialManager implemented by this class saves the provided credentials in a secure manner (using a SecureString for the passwords). The credentials do expire over time, using two different expiration schemes:

  • Absolute expiration: Regardless how often a credential is used in a given timespan, the credential will expire after this timespan.
  • Sliding expiration: If a credential is not used for a specified timespan, the credential will expire.

The two expiration schemes can be used in together.

The SecurityContext

If you want to execute a region of code in the context of another user, you can use this class. The base class must be provided with a callback that provides the logon credential used for the impersonation. The main advantage, however, is the possibility to use the using construct, as shown in this example:

    1 using (SecurityContext context = new SecurityContext("target", AcquireCallback, true))

    2 {

    3     DoSomething();

    4 }

The AcquireCallback method could be implemented like this example:

    1 privatevoid AcquireCredential(object source, AcquireCredentialCallbackArgs e)

    2 {

    3     CmdLinePromptForCredential prompt;

    4 

    5 

    6     prompt = new CmdLinePromptForCredential(e.TargetName,

    7         string.IsNullOrEmpty(e.Username) ? _Username : e.Username,

    8         QueryCredentialOptions.ExcludeCertificates | QueryCredentialOptions.DoNotPersist |

    9         QueryCredentialOptions.ValidateUsername);

   10 

   11     e.Cancel = !prompt.Prompt();

   12     e.Username = prompt.Username;

   13     e.Password = prompt.Password;

   14 }

If do not want to impersonate immediately, you can set the last parameter in the SecurityContext constructor to false. You can either use the context.Impersonate() method later, or use the StartProcess method to run a process under the usercontext of the SecurityContext.

Two derived classes are implemented, which simplyfies the usage of the SecurityContext object: The DialogSecurityContext, and the CmdLineSecurityContext. The first one is designed for use in a Windows Forms application, as it utilizes the QueryCredentialDialog to get a valid username/password combination. The latter one uses the CmdLinePromptForCredential class, which is essentially a console version of the QueryCredentialDialog.

The credential acquired by the impersonation context is cached in the Credential manager for future use. Unless the credential expires, the user will not be asked again to enter a username/password.

The SecureStringDecryptor

The SecureString is a very usefull class when handling sensitive data, because its contents are encryped most of the time. Unfortunately, if you want to use such a string to encrypt using the Rijndael algorithm, you are stuck, because it does not accept a SecureString as a key. It requires an array of bytes. So, the right thing to do is to use one of the DeriveBytes classes: PasswordDeriveBytes or the Rfc2898DeriveBytes class. Because the PasswordDeriveBytes.GetBytes method is marked obsolete, you should stick to the latter one.

Next disappointment: Neither one of the DeriveBytes implementations accept a SecureString as a password. Only a string or a byte array. At this point, you can use the SecureStringDecryptor: It decrypts the SecureString into a pinned byte array, that can then be used to initialize one of the DeriveBytes classes. Once you are finished, just call the Dispose method of the SecureStringDecryptor, and the password array is zeroed out. Here is an example in C#:

    1 privatevoid EncryptData(byte[] dataToProtect)

    2 {

    3     SecureString secret = GetSecret();

    4     byte[] salt = GetSalt();

    5     DeriveBytes db;

    6     Rijndael rijndael;

    7     ICryptoTransform transform;

    8 

    9     using (SecureStringDecryptor helper = newSecureStringDecryptor(secret)) {

   10         db = newRfc2898DeriveBytes(helper.Password, salt, 1024);

   11     }

   12     rijndael = Rijndael.Create();

   13     transform = rijndael.CreateEncryptor(db.GetBytes(16), rijndael.GenerateIV())

   14 

   15     return transform.TransformFinalBlock(dataToProtect, 0, dataToProtect.Length);

   16 }

ChangeLog

Version 1.3

  • The ImpersonationContext class has been renamed to SecurityContext
  • The DialogImpersonationContext class has been renamed to DialogSecurityContext
  • The DialogImpersonationContextInfo class has been renamed to DialogSecurityContextInfo

  • The CmdLineImpersonationContext class has been renamed to CmdLineSecurityContext

Version 1.2

  • Added a Confirm and Invalidate method the the SecurityContext. This is useful when generic credentials are queried from the user instead of Windows credentials (for example to authenticate to a remote webserver). In this case you can manually confirm or invalidate the credential.
  • Added a Username and Password property to the SecurityContext class. Again this is important when working with generic credentials.
  • Added a new class: SecureStringDecryptor. This class allows you to get a byte array containing the decrypted content of a SecureString instance. But be careful when using this class. Do not convert the byte array to a string instance. Do not make a copy the byte array. You should use this class as described in this article only. Otherwise, most of the security advantages of the SecureString class are gone. You have been warned!

Version 1.0

Initial Release

Downloads

infiniTec.Security_Documentation.zip (102,170 Bytes)
Documentation
infiniTec.Security_source.zip (28,660 Bytes)
Full source code
InfiniTec.Security_release.zip (31,638 Bytes)
Strong named release binaries with debug symbols.

Technorati:

Posted by Henning Krause on Tuesday, November 29, 2005 12:00 AM, last modified on Friday, December 30, 2005 12:00 PM
Permalink | Post RSSRSS comment feed

Active Directory Helper library

When working with Exchange, it is often necessary to fetch some informations from Active Directory. This helper library has this functionality:

  • Find a user in a multi-domain forest based on his domain\username.
  • Construct the URL for mailbox of a given user, based on his domain\username. This URL can then be used for WebDAV queries.
  • Get a DirectorySearcher for the Global Address list and all the other defined address lists

Full source included, and, believe it or not, documentation :-)

Downloads

InfiniTec Helper Library for Active Directory.zip (52,005 Bytes)
Documentation file as CHM file
ActiveDirectoryHelperBinaries.zip (61,371 Bytes)
Binaries, compiled in release version
source.zip (388,464 Bytes)
Full Source

Technorati:

Posted by Henning Krause on Saturday, August 27, 2005 12:00 AM, last modified on Saturday, August 27, 2005 12:00 AM
Permalink | Post RSSRSS comment feed