Implementing Simple Role-Based Security in Managed C++

From Kate Gregory's Codeguru column, "Using Visual C++ .NET".

-->

The .NET Framework makes things so much simpler for developers who care about security. There are a lot of useful ways to make sure that only authorized users update the database, place orders, or otherwise interact with your system. In addition, code access security ensures that only trusted code can perform certain tasks, no matter who runs the code.

In this column I focus only on one small topic: how to establish that the user running your application is authorized to perform some subtask within the application. You can extend the examples here to any application you write in Managed C++.

Who's There?

Here's a simple console application:

using namespace System;
using namespace System::Security::Principal;

// This is the entry point for this application
int _tmain(void)
{
    WindowsIdentity* Identity = WindowsIdentity::GetCurrent();
    WindowsPrincipal* Principal = new WindowsPrincipal(Identity);

    //Print the values.
    Console::WriteLine("Principal Values for current thread:");
    Console::WriteLine("Name: {0}", 
                       Principal->Identity->Name);
    Console::WriteLine("Type: {0}", 
                       Principal->Identity->AuthenticationType);
    Console::WriteLine("IsAuthenticated: {0}",
                       __box(Principal->Identity->IsAuthenticated));
    Console::WriteLine();
    Console::WriteLine();
    Console::WriteLine("Identity Values for current thread:");
    Console::WriteLine("Name: {0}", Identity->Name);
    Console::WriteLine("Type: {0}", Identity->AuthenticationType);
    Console::WriteLine("IsAuthenticated: {0}", 
                       __box(Identity->IsAuthenticated));
    Console::WriteLine("IsAnonymous: {0}", 
                       __box(Identity->IsAnonymous));
    Console::WriteLine("IsGuest: {0}", __box(Identity->IsGuest));
    Console::WriteLine("IsSystem: {0}", __box(Identity->IsSystem));
    Console::WriteLine("Token: {0}", Identity->Token.ToString());
    return 0;
}

The first line of this code gets the identity under which the application is running. That should be you, since the application doesn't contain any code to change the identity. Then it gets a principal based on that identity. The remainder of the code illustrates some useful properties of the identity and principal objects. Notice the use of __box to wrap up the Boolean values, and the ToString method to convert non-string values to strings.

When I run this application, it prints out:

Principal Values for current thread:
Name: GREGORY\kate
Type: NTLM
IsAuthenticated: True


Identity Values for current thread:
Name: GREGORY\kate
Type: NTLM
IsAuthenticated: True
IsAnonymous: False
IsGuest: False
IsSystem: False
Token: 304

You could do a little security code of your own at this point, comparing the name of the identity or principal with a list of names of authorized users that you have stored somewhere. But that will require you to write code to manage user lists: adding and deleting users, changing their authority code, and so on. I prefer to leverage Windows groups. Even if they aren't in use by the people who'll be running the application, it's less work to teach people to use Windows groups than to write your own equivalent administration section.

What Group Are You In?

This code checks to see if a user is in a particular Windows group or not:

String* role = "GREGORY\\Domain Users";
if (Principal->IsInRole(role))
    Console::WriteLine("You are a domain user");
else
    Console::WriteLine("You are not a domain user");

role = "BUILTIN\\Administrators";
if (Principal->IsInRole(role))
    Console::WriteLine("You are an administrator");
else
    Console::WriteLine("You are not an administrator");

role = "JSERV\\CodeGuru";
if (Principal->IsInRole(role))
    Console::WriteLine("You are a Code Guru");
else
    Console::WriteLine("You are not a Code Guru");

When I run this code, it prints out:

You are a domain user
You are an administrator
You are a Code Guru

For you, it almost certainly will not. Notice the three different prefixes in the role strings:

  • GREGORY is my domain. Use this prefix when you're referring to a group that has been created on your domain controller.

  • BUILTIN refers to built-in groups such as Administrators, created when you install Windows 2000 or NT.

  • JSERV is my machine name. Use this prefix when you're referring to a local group.

You can test this code by creating a local group, and adding yourself to it. If you've never done that before, you can use Computer Management. Right-click My Computer on your desktop and choose Manage. Expand the Local Users and Groups section, then click on Groups.



Click here for larger image

Choose Actions, New Group to create a group. Name it CodeGuru, and click Add to add users to it. Add yourself. Then log off Windows and log back on again to update your security profile, and run the application again. Now you should be told you are a Code Guru. (And don't forget about the logging off and logging on. You'll do a lot of that when you're testing security code.)

You could use tests of IsInRole() to decide whether to enable or disable a button. Or you could throw a security exception when the wrong kind of user tried to do something. If you'd like to do that, you're better off using permissions sets.

Permission Sets

Permission sets can be used imperatively or declaratively, the documentation likes to say, which means you can either call a function or add an attribute. Let's start with the imperative way.

I added a class to my console project. It isn't even a managed class, and it only has a constructor and destructor:

class AccessControlled
{
public:
    AccessControlled(void);
    ~AccessControlled(void);
};

(I don't need a destructor, but I right-clicked the project in Class View and chose Add, Add Class and added a generic C++ class, and that gave me a constructor and destructor.) The implementation file looks like this:

#include "StdAfx.h"
#using 
#include "accesscontrolled.h"

using namespace System;
using namespace System::Security::Permissions;
using namespace System::Security::Principal;

AccessControlled::AccessControlled(void)
{
    AppDomain::CurrentDomain->SetPrincipalPolicy(
                          PrincipalPolicy::WindowsPrincipal);
    //String* role = "BUILTIN\\Administrators";
    String* role = "JSERV\\CodeGuru";
    PrincipalPermission* p = new PrincipalPermission(0,role);
    p->Demand();
}

AccessControlled::~AccessControlled(void)
{
}

The first line of this constructor is vital: without it the code will compile but will always react as though you are not in the group. The SetPrincipalPolicy instructs the framework to create WindowsPrincipal objects by default.

The code creates a PrincipalPermission object that represents being in a particular role. (The role names used in this example follow the same rules as those used for IsInRole().) The first parameter is 0 here, to indicate that any user identity is acceptable. You can pass in a string representing a specific user if you wish. Having created a PrincipalPermissions object, you now demand that the individual running this application meets the conditions in that object. If the demand fails, a security exception will be thrown.

I added a few more lines to _tmain() to create an AccessControlled object, triggering a call to the constructor:

AccessControlled* ac = new AccessControlled();
Console::WriteLine("created the access controlled object");

When I am in the CodeGuru group, this works. When I am not, it throws a SecurityException.

The other way to use PrincipalPermissions is as an attribute, declaratively. This requires the class to be garbage collected, since only managed classes can have attributes:

using namespace System::Security::Permissions;

[PrincipalPermissionAttribute(SecurityAction::Demand, 
                              Role = "BUILTIN\\Administrators")]
__gc class AccessControlled
{
public:
    AccessControlled(void);
    ~AccessControlled(void);
};

Now the call to SetPrincipalPolicy must be before the attempt to construct the AccessControlled object:

AppDomain::CurrentDomain->
       SetPrincipalPolicy(PrincipalPolicy::WindowsPrincipal);

AccessControlled* ac = new AccessControlled();
Console::WriteLine("created the access controlled object");

The constructor itself no longer needs any security code. No-one can create or use an AccessControlled object unless they are in the specified role.

Conclusion

When you build .NET applications, you get a lot of security code without writing it yourself. It's easy to determine who a user is, and what role (Windows group) the user is in. You can deny access to specific methods or entire classes based on the user's identity. Now there's no excuse for skimping on security. Oh, and one more tip: if you're administrator on your own machine, stop it! Give yourself another account with less power, and find out what life is like for the users of those applications you write. I assure you, it will be an eye-opener.

About the Author

Kate Gregory is a founding partner of Gregory Consulting Limited (www.gregcons.com). In January 2002, she was appointed MSDN Regional Director for Toronto, Canada. Her experience with C++ stretches back to before Visual C++ existed. She is a well-known speaker and lecturer at colleges and Microsoft events on subjects such as .NET, Visual Studio, XML, UML, C++, Java, and the Internet. Kate and her colleagues at Gregory Consulting specialize in combining software develoment with Web site development to create active sites. They build quality custom and off-the-shelf software components for Web pages and other applications. Kate is the author of numerous books for Que, including Special Edition Using Visual C++ .NET.

# # #



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The latest release of SugarCRM's flagship product gives users new tools to build extraordinary customer relationships. Read an in-depth analysis of SugarCRM's enhanced ability to help companies execute their customer-facing initiatives from Ovum, a leading technology research firm.

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds