Role-Based Security in .NET5

for RuBoard

Role-Based Security in .NET [5]

[5] The discussion in this section is relevant for intranets or other scenarios where users will have Windows user accounts on the servers or domains. See the later section "Forms-Based Authentication" for a discussion of security appropriate to the scenario of a public Web site.

Most people have at least an intuitive understanding of users and passwords. MTS and COM+ have provided an easy-to-understand security system based on roles . The best place to start a more detailed look at .NET security is with identities and roles. We will look at this from the point of view first of a Windows application and then of ASP.NET.

Principals and Identities

Each thread has associated with it a CLR principal. The principal contains an identity representing the user id that is running that thread. The static property Thread.CurrentPrincipal will return the current principal associated with the thread.

Principal objects implement the IPrincipal interface. IPrincipal has one method and one property. The Identity property returns the current identity object, and the method IsInRole is used to determine whether a given user is in a specific role. The RoleBasedSecurity example illustrates the use of principals, identities, and roles.

Currently there are two principal classes in the .NET framework: WindowsPrincipal and GenericPrincipal . The GenericPrincipal class is useful if you need to implement your own custom principal. The WindowsPrincipal represents a Windows user and its associated roles.

Since the RoleBasedSecurity example is a Windows (console) application, we will have a WindowsPrincipal associated with the CurrentPrincipal property.

 ...  [6]  IPrincipal ip = Thread.CurrentPrincipal;  WindowsPrincipal wp = ip as WindowsPrincipal;  if (wp == null)    Console.WriteLine("Thread.CurrentPrincipal is NOT a                                       WindowsPrincipal");   else    Console.WriteLine("Thread.CurrentPrincipal is a                                       WindowsPrincipal");  ... 

[6] The program starts out with a demand for a SecurityPermission and then proceeds to set the AppDomain principal policy. While the reasons for this will be discussed later, the quick answer is to make sure that the example functions properly on your machine. If you get an exception, you will have to set the policy on your local machine to allow you to run the example. On a vanilla system with a standard install, this should not happen. What to do if it does happen is discussed later in the chapter.

An identity object implements the IIdentity interface. The IIdentity interface has three properties:

  • Name is the string associated with the identity. This is given to the CLR by either the underlying operating system or the authentication provider. ASP.NET is an example of an authentication provider.

  • IsAuthenticated is a Boolean value indicating whether the user was authenticated or not.

  • AuthenticationType is a string that indicates which authentication was used by the underlying operating system or authentication provider. Examples of authentication types are: Basic, NTLM, Kerberos, Forms, or Passport.

Substitute Name of Your Machine in the Examples

In several of the examples the machine name MICAH is used. You should substitute the appropriate machine or domain name when you run the samples on your computer.

There are several types of identity objects. Since this is a Windows program, we will have a WindowsIdentity object associated with the WindowsPrincipal . The example next prints out the property information associated with the identity object.

 IIdentity ii = ip.Identity;  Console.WriteLine("Thread.CurrentPrincipal Name: {0}                 Type: {1} IsAuthenticated: {2}", ii.Name,                ii.AuthenticationType, ii.IsAuthenticated); 

On my machine this is printed out:

 Thread.CurrentPrincipal Name: MICAH\mds Type: NTLM                                       IsAuthenticated: True 

The operating system on the machine MICAH using the NTLM protocol has authenticated the user running this program to be "mds." The sample then validates that this is indeed a WindowsIdentity object. The WindowsIdentity object has additional properties and methods besides those of the IIdentity interface. One of them is the Win32 account token id associated with the currently running user.

 WindowsIdentity wi = wp.Identity as WindowsIdentity;  if (wi != null)    Console.WriteLine("WindowsPrincipal.Identity Name: {0}      Type: {1} Authenticated: {2} Token: {3}", wi.Name,  wi.AuthenticationType, wi.IsAuthenticated, wi.Token); 

You can use the name of the user to decide (authorize) whether the user has the rights to undertake certain actions by refusing to execute certain code paths.

.NET Windows Roles

Instead of checking each individual user name, you can assign users to roles . You can then check to see if a user belongs to a certain role. The standard administrators group is an example of how a role works. You do not have to individually assign a user identity all the privileges that an administrator has and then check to see if individual users have certain privileges. Instead, you just assign the user to the administrators group. Code then checks to see if a user is in the administrators group before attempting actions such as creating a new user. .NET roles are separate from COM+ roles.

You define roles by defining groups in NT4 or Windows2000. Each group represents one role. Go to the Control Panel and select Administrative Tools. From the Administrative Tools list select Computer Management. In the Computer Management MMC snap-in expand the Local Users and Groups node. As Figure 12-1 shows, if you select Groups you will see all the Groups defined on your machine.

Figure 12-1. Groups defined on a machine.

graphics/12fig01.gif

Some groups, such as Administrators and Guests, are "built in" because they are predefined for you. CustomerAdmin is a user-defined group that represents administrators who have the right to modify Acme customer information.

To add a new group to the local machine, right-mouse-click on the Groups node and select "New Group." A dialog box you can fill in pops up. Figure 12-2 shows this dialog box filled for a new group entitled "HotelAdmin" which is designed to have all users on the machine who can add or modify information about hotels in the HotelBroker system. Clicking the Create button will add the group to the system. You can use the Add and Remove buttons to add or remove users from the group.

Figure 12-2. Dialog to create a HotelAdmin group.

graphics/12fig02.gif

To modify an existing group, select that group, right-mouse-click, and select Properties. Clicking the Add button will bring up a dialog of all users on the system. You can then select users and add them to the group. Figure 12-3 shows a user about to be added to the HotelAdmin group. The Remove button is used to remove users from the group.

Figure 12-3. User JaneAdmin about to be added to the HotelAdmin group. User mds has already been added.

graphics/12fig03.gif

In addition to creating a HotelAdmin group, you should also create a CustomerAdmin group with JaneAdmin as a member using the same procedure we just described. Note that the JaneAdmin user need not, and in fact should not, be a member of the Administrators group. Users should run with the minimum privilege required. Within code you qualify the name using the domain or machine name. The CustomerAdmin role is referred to as "MICAH\\CustomerAdmin." For groups that are preinstalled , such as the Administrators group, you use the "BUILTIN" prefix ”for example, "BUILTIN\\Administrators." To avoid translation and internationalization problems, the System.Security.Principal.WindowsBuiltInRole enumeration can be used to refer to built-in roles. Instead of using the "BUILTIN\\Administrators" string you can refer to the Administrators group as WindowsBuiltInRole.Administrator .

The RoleBasedSecurity example now checks to see if the current user is in a role. You can either pass the role as a string or use the WindowsBuiltInRole enumeration. Remember to modify the programs to use the name of your machine when you run the book samples on your computer.

 string adminRole = "MICAH\CustomerAdmin";  bool inRole = wp.IsInRole(adminRole);  Console.WriteLine("In CustomerAdmin role?: {0}", inRole);  inRole = wp.IsInRole(WindowsBuiltInRole.Administrator);  Console.WriteLine("Is in Administrators group: {0}",    inRole);  inRole = wp.IsInRole(WindowsBuiltInRole.Guest);  Console.WriteLine("Is in Guests group: {0}", inRole);  inRole = wp.IsInRole(WindowsBuiltInRole.User);  Console.WriteLine("Is in Users group: {0}", inRole); 

Other Identity Classes

Now let us look in more detail at the other Identity classes. Currently there are four in the .NET Framework:

  • FormsIdentity is used by the FormsAuthenticationModule class. We will discuss this class when we discuss ASP.NET forms authentication.

  • GenericIdentity can represent any user. This class is used with the GenericPrincipal for generic or custom identities and principals.

  • PassportIdentity is used with Passport authentication. Since we do not discuss Passport, we will not discuss this class.

  • WindowsIdentity represents a Windows user. A Windows Principal instance will have a WindowsIdentity instance as its Identity property. For authenticated users, the type of authentication used (NTLM, Kerberos, etc.) is available.

Note that the properties of the IIdentity interface are read-only and therefore cannot be modified.

Even if your users are unauthenticated, you can get the WindowsIdentity for any thread using the static method WindowsIdentity.GetCurrent to get the WindowsIdentity instance of the current user. [7] You can then use the WindowsPrincipal constructor to build a WindowsPrincipal instance from this WindowsIdentity .

[7] We discuss what this represents in the next section.

The HotelBrokerAdminstration program has been modified so that you cannot run it if you are not in the HotelBrokerAdmin role. See the file MainAdminForm.cs in the directory HotelBrokerAdministration Roles .

 static void Main()  {    ...    IPrincipal ip;    ip = Thread.CurrentPrincipal;     string hotelAdminRole = "MICAH\HotelAdmin";    bool inRole = ip.IsInRole(hotelAdminRole);    if (inRole == false)    {      MessageBox.Show("You cannot run this program since you                            are not a Hotel Administrator.",                          "Acme Customer Management System",                           MessageBoxButtons.OK,                           MessageBoxIcon.Exclamation);      return;    }    Application.Run(new MainAdminForm());  } 

ASP.NET Roles

Now that we have a fundamental understanding about principals, identities and roles, we can apply it to our AcmeReservationSystem Web site. The Web site has been modified so that you can choose to link to a HotelAdministration page where you can add, modify, or delete the hotels that are part of the HotelBroker system. This example is found in the Step0 subdirectory of the ASP.NET Roles directory . To run this example, make sure that the Step0 directory is a virtual directory with the name AcmeWebSecurityStep0. Figures 12-4 and 12-5 show the new Web pages.

Figure 12-4. The new Acme Home Page with the link to the administration page.

graphics/12fig04.gif

Figure 12-5. The administration page for the AcmeReservation system.

graphics/12fig05.gif

Since at this stage in the book you have a lot of experience with .NET, we do not spell out the details of building the various ASP.NET examples. Please consult the file readme.txt in the current chapter directory if you would like some pointers.

At this point there is no security associated with these pages. Anyone who can log into the Web site can access the administration page and modify the hotel information. We have also modified the login page to print out the current principal and identity information associated with the application as well as the information associated with the current WindowsIndentity .

 string text;  IPrincipal ip;  ip = Thread.CurrentPrincipal;  string principalText = "CurrentPrincipal is of type " +                                  ip.GetType().ToString();  IIdentity ii = ip.Identity;  principalText = principalText + "\n   " +                              "Is user authenticated?: " +                             ii.IsAuthenticated.ToString();  text = principalText;  WindowsIdentity wi = WindowsIdentity.GetCurrent();  string identityText = "Current Windows Identity: " + "\n       " + "Name: " + wi.Name + "\n   IsAuthenticated?:" +        wi.IsAuthenticated + "\n   AuthenticationType:" +        wi.AuthenticationType;  text = text + "\n" + identityText;  IdentityInfo.Text = text; 

As Figure 12-6 illustrates, looking at the information on the login page we find that we have an unauthenticated generic principal for the thread, yet the current WindowsIdentity indicates that we are running as the authenticated SYSTEM account. What does this mean? In the previous examples we used the IsInRole method associated with the CurrentPrincipal . But that user is now not authenticated, so that method will always return false!

Figure 12-6. Principal and identity information about the Step0 AcmeWebSecurity Web site.

graphics/12fig06.gif

Operating System Identity and CLR Identity

As we mentioned at the start of the chapter, .NET security sits on top of the underlying operating-system security. The identity associated with the thread by the CLR and the identity associated with the thread by the underlying operating system are not the same. The identity of the thread from the operating-system perspective is reflected by the setting of the WindowsIdentity object returned by the static Windows.Identity.GetCurrent method. The CLR identity is reflected by the value of the Thread.CurrentPrincipal object. [8] To go back to the example mentioned at the start of the chapter, if you access a file from within .NET, both the managed and unmanaged identities must have rights to the file within their respective environments.

[8] The reason why these were identical in the RoleBasedSecurity example is that we set the application domain principal policy in the example to be PrincipalPolicy.WindowsPrincipal . With the default ASP.NET settings in the config.web , the PrincipalPolicy.UnauthenticatedPrincipal policy is used. For that policy, Thread.CurrentPrincipal returns an unauthenticated GenericPrincipal object. We will discuss principal policy later.

What values the current WindowsIdentity and Thread.CurrentPrincipal have are set in two places: IIS Settings and the ASP.NET configuration files.

Unauthenticated Users

Every machine that runs .NET has a machine.config file that has the default configuration for the computer. This file is found in the \WINNT\Microsoft.NET\Framework\v1.0.2914\CONFIG directory, where v1.0.2914 would be replaced by the version of Microsoft.NET that is running on your machine. A Web or Web Service application may have a config.web file that has the configuration settings for that application. The settings for config.web affect all applications in the directory where it lives and all its subdirectories. Config.web files in the subdirectories override the settings in the higher-level directories.

If you look in the settings in config.web for the Step0 project, you will see the following settings:

 <identity impersonate="false" />  <authentication mode="None" /> 

The first value sets the unmanaged identity returned by the current WindowsIdentity . Since it is set to false, the default operating system identity that ASP.NET runs as will be the SYSTEM account. Since this has broad privileges on the local machine, the Web application can run unimpeded, but this is undesirable from a security perspective, as we will discuss later. The second sets the managed, CLR-based identity returned by Thread.CurrentPrincipal . Setting it to "None" means use the default or GenericPrincipal . The login page displays the current security configurations, as was shown in Figure 12-6. Here is the relevant output.

 CurrentPrincipal is of type  System.Security.Principal.GenericPrincipal     Is user authenticated?: False     Name:  Current Windows Identity:     Name: NT AUTHORITY\SYSTEM     IsAuthenticated?:True     AuthenticationType:NTLM 

If you do not have a config.web file, the authentication mode set in machine.config is "Windows." Now if we set the authentication mode in our local config.web to "Windows," we see the following output:

 CurrentPrincipal is of type  System.Security.Principal.  WindowsPrincipal  Is user authenticated?: False     Name:  Current Windows Identity:     Name: NT AUTHORITY\SYSTEM     IsAuthenticated?:True     AuthenticationType:NTLM 

Thread.CurrentPrincipal now returns a WindowsPrincipal , but it is still unauthenticated and has no name associated with it. Other values for the authentication mode are Forms and Passport.

Let us set the authentication mode back to "None." But now let us set the identity impersonate to "true":

  <identity impersonate="true" />  <authentication mode="None" /> 

Here are the results:

 CurrentPrincipal is of type  System.Security.Principal.GenericPrincipal     Is user authenticated?: False     Name:  Current Windows Identity:  Name: MICAH\IUSR_MICAH   IsAuthenticated?:True  AuthenticationType:NTLM 

Where does the identity MICAH\IUSR_MICAH [9] come from? This user is the identity that is set in the properties for this Web application for anonymous access. Select this Web application in the Internet Services Manager, right-mouse-click, and select Properties. Navigate to the Directory Security tab. Click on the Edit button associated with Anonymous access and authentication control. Note that the Anonymous access checkbox is checked. Click the Edit button associated with Account used for anonymous access and you will see this user account listed. Figure 12-7 shows the related dialog boxes. You could change this setting to some other account, but this is the default value set when IIS is installed.

[9] As usual, MICAH is the name of my machine. Yours will be different.

Figure 12-7. Internet Services Manager settings for anonymous access.

graphics/12fig07.gif

Reset the authentication mode back to "Windows" and run again. [10] We still do not see an authenticated principal for the managed Thread.CurrentPrincipal identity.

[10] To duplicate the results in the next section make sure you reset the authentication mode back to "Windows" now.

Authenticated Users

Now let us use the Internet Services Manager to set our Web application to use Windows Integrated Security instead of anonymous access, as shown in Figure 12-8. Right-click over "AcmeWebSecurityStep0" in the left pane and choose Properties from the context menu. We uncheck the anonymous access box and check the Integrated Windows authentication box.

Figure 12-8. Internet Services Manager settings for authenticated access.

graphics/12fig08.gif

Running our application gets the following results:

 CurrentPrincipal is of type  System.Security.Principal.  WindowsPrincipal   Is user authenticated?: True   Name: MICAH\Administrator  Current Windows Identity:  Name: MICAH\Administrator  IsAuthenticated?:True     AuthenticationType:NTLM 

We now have an authenticated Thread.CurrentPrincipal whose identity is the same as the current WindowsIdentity . They are associated with whatever user account is currently logged in. Both the managed and unmanaged principals are the same. Now uncheck the Integrated Windows authentication box and check the Basic authentication box in the Internet Services Manager dialog, as shown in Figure 12-9.

Figure 12-9. Internet Services Manager settings for Basic authentication.

graphics/12fig09.gif

If you run the application now, you will have a user and password dialog appear when your run the Web application, as shown in Figure 12-10.

Figure 12-10. Dialog for entering a Windows user name and password.

graphics/12fig10.gif

You now have to enter the user name and password associated with an account on the system. Again, when the login page appears, both the Thread.CurrentPrincipal and current WindowsIdentity identities are the same, but they are associated with whichever user account you entered into the dialog box, as shown:

 CurrentPrincipal is of type  System.Security.Principal.WindowsPrincipal     Is user authenticated?: True     Name:  MICAH\JaneAdmin  Current Windows Identity:     Name:  MICAH\JaneAdmin  IsAuthenticated?:True     AuthenticationType:NTLM 

How did the identity associated with the CurrentPrincipal get set to be the same as the WindowsIdentity ? ASP.NET sets the CurrentPrincipal to match the HttpContext.User property. In a Windows application you have no choice but to use the Thread.CurrentPrincipal. Within ASP.NET it is safer to use the HttpContext.User property. Within ASP.NET you can access the HttpContext.User property through the User object. Step 1 of ASP.NET Roles adds the following code to the Page_Load method of main.aspx.cs :

 if (User.IsInRole("MICAH\HotelAdmin"))    HotelAdminLink.Visible = true;  else    HotelAdminLink.Visible = false; 

The Internet Services Manager security should be set to at Windows Integrated security. The following settings are still in web.config :

 <identity impersonate="true" />  <authentication mode="Windows" />. 

Therefore, any user logged into Windows who is a member of the HotelAdmin group, will see the Administration link, otherwise the link will not appear. Of course, what name you enter into the login page has nothing to do with what you see. It is the identity associated with the thread that matters.

If you want to test your Web application as a different user, you do not have to log out and log in as that user. Navigate to Internet Explorer on the Start Menu, and right-mouse-click while holding down the shift key. You will see a menu item "Run As..." (see Figure 12-11). Select it, and in the dialog box that comes in, log in as the user you want to use. That particular instance of Internet Explorer will be running under that user identity.

Figure 12-11. RunAs Menu item to run as a different user.

graphics/12fig11.gif

Problems with Impersonation

It would seem that we need only make sure that the user id the thread impersonates is a member of the HotelAdmin group and does not have any more privileges than are needed (i.e., is not System or an administrator, with no ACL rights to any unnecessary files on the server) and then everything will be just fine.

Unfortunately, life is not so simple. Impersonation was designed to be used by a server to alter its rights by running a thread as another user. When the server is done impersonating a user, however, it can revert to its original set of rights by calling the RevertToSelf Win32 API. If you call out to a third party or any unmanaged code DLL running in your process, and it made a call to RevertToSelf , it would be running as SYSTEM. As SYSTEM this DLL could cause havoc on your system if it were malicious or just buggy .

Step 2 of ASP.NET Roles uses the following code to do this: [11]

[11] You use the PInvoke interop facility to access the Win32 function. D11Import and PInvoke are discussed in Chapter 14.

 using System.Runtime.InteropServices;  ...  [DllImport("Advapi32.dll")]    public static extern bool RevertToSelf();  ...  string text;   text = "Windows Identity: " +                 WindowsIdentity.GetCurrent().Name + "\n";  text = text + "CLR Identity: " + User.Identity.Name +                                                     "\n";  text = text + "Calling RevertToSelf()...\n";  bool bRet = RevertToSelf();  text = text + "Windows Identity: " +                 WindowsIdentity.GetCurrent().Name + ``\n;  text = text + "CLR Identity: " + User.Identity.Name +                                                     "\n";  txtInfo.Text = text; 

On the Acme Home Page, calling RevertToSelf changes the identity of the thread from the point of view of unmanaged code. The identity from the CLR perspective is unchanged. The HotelAdmin link will be visible or not, depending on the original impersonated identity. Figure 12-12 shows the results.

Figure 12-12. Acme Home page showing changes in Thread Identities.

graphics/12fig12.gif

To avoid running as the SYSTEM account, you can set the identity of the process that your Web application runs under. [12] , [13] If you look in machine.config under the <processModel> tag, you will find the enable, userName, and password attributes.

[12] SYSTEM is the identity of the Process Token for your application. Unless impersonating, all threads in the process would use that token. Calling RevertToSelf removes the impersonation from the thread and reverts back to whatever identity the Process Token had.

[13] On IIS 5, the identity is inherited from inetinfo.exe. If you configure inetinfo and iisadmin to run with a different identity, that will be the identity of the aspnet_wp process. On IIS 6 with Windows.NET server, ASP.NET does not run its own process model and inherits identity from the IIS worker process. This worker process is configurable and defaults to Network.Service. This is a much better default then SYSTEM.

 <processModel enable="true"  ...  userName="SYSTEM" password="AutoGenerate"  ...  /> 

By default, your application process runs under the SYSTEM account. You can modify this value in the machine.config file only. We could change the value to be a specific user name: [14]

[14] You will have to stop and start the WWW service on your machine to make the changes to machine.config effective.

 <processModel enable="true"  ...  userName="JaneAdmin" password="xyz"  ...  /> 

Figure 12-13 shows the results. [15] As you can see, the password for this user is written in plain text inside machine.config . By default, machine.config is readable by everyone, so if you use this approach, rights to that file should be restricted.

[15] If you have problems running with a user id you supply here, that id will probably need ACL rights to various system directories on your machine, such as the ASP.NET temporary file directory.

Figure 12-13. Results of using RevertToSelf when a specific user is set in <processModel>.

graphics/12fig13.gif

You can also use a setting called MACHINE which uses an account called ASPNET, which is a member of the guest group only and therefore has limited access rights. The ASP.NET setup does add access rights for that user to system directories to enable it to run, such as the \WINNT\Microsoft.NET\Framework\vx.xxx directory. Figure 12-14 shows the results of this setting.

Figure 12-14. Results of using RevertToSelf when generic ASP.NET account is used in <processModel>.

graphics/12fig14.gif

 <processModel enable="true"  ...  userName="machine" password="AutoGenerate"  ...  /> 

To summarize, if impersonation is turned off, as Figure 12-15 shows, then you would run as whatever identity is specified in the process model. If you use anonymous access, then Figure 12-16 shows the results you would expect, that the CLR thread identity is unauthenticated.

Figure 12-15. Impersonation turned off, MACHINE specified in <processModel>.

graphics/12fig15.gif

Figure 12-16. Impersonation turned off, anonymous access, MACHINE specified in <processModel>.

graphics/12fig16.gif

This discussion also makes clear that the ACLs on machine.config and web.config should be set so that only administrators can modify the file. Who can read the files should be restricted appropriately. You also have to guard against someone browsing and downloading from those files.

Specifying Users and Groups for Access in Web.Config

ASP.NET allows you to specify groups and users who are allowed to access the Web site. Inside the <authorization> section of web.config you can use the <allow> and <deny> elements with user accounts or groups. To specify groups you use the roles attribute; to specify users you use the users attribute. The asterisk (*) symbol used with one of those elements means all. A question mark (?) used with a user attribute means "anonymous access."

 <allow roles="MICAH\HotelAdmin" users="MICAH\Peter">  <deny users="MICAH\John"> 

A reference to a specific user overrides their membership in a group or a wildcard. Deny references take precedence over allow references. These settings do not help you assign users to particular roles or prevent access to different areas of the Web site. Only access to the entire Web site is controlled.

for RuBoard


Application Development Using C# and .NET
Application Development Using C# and .NET
ISBN: 013093383X
EAN: 2147483647
Year: 2001
Pages: 158

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net