views:

1103

answers:

6

Hi.

I need to know how to go about implementing general security for a C# application. What options do I have in this regard? I would prefer to use an existing framework if it meets my needs - I don't want to re-invent the wheel.

My requirements are as follows:

  • the usual username/password authentication
  • manageing of users - assign permissions to users
  • managing of roles - assign users to roles, assign permissions to roles
  • authorization of users based on their username and role

I am looking for a free / open-source framework/library that has been time-tesed and used by the .Net community.

My application takes a client/server approach, with the server running as a windows service, connecting to a SQL Server database. Communication between client and server will be through WCF.

One other thing that is important is that I need to be able to assign specific users or roles permissions to View/Update/Delete a specific entity, whether it be a Customer, or Product etc. For e.g. Jack can view a certain 3 of 10 customers, but only update the details of customers Microsoft, Yahoo and Google, and can only delete Yahoo.

+1  A: 

WCF have rich security related functionality provides both authorization and authentication. In details here: http://msdn.microsoft.com/en-us/library/ms735093.aspx

Ray
+3  A: 

Look into ASP.NET's Membership Providers. I don't think the out of box SQLMembershipProvider will work in your case but it's easy enough to roll your own provider.

CptSkippy
I rolled my own for an internal app that already has a database with users set up. Definitely recommend it.
Ash M
+2  A: 

I would take a look at something like CSLA.net: Expert C# 2008 Business Objects

It should provide everything you require.

Chris Andrews
This option looks promising. I'm researching it further. Do you have any more info / examples / tutorials?
Saajid Ismail
Over at Rocky Lhotka's site the latest versions of CSLA all have Samples as well as Test projects...http://www.lhotka.net/cslanet/Download.aspx
chris.w.mclean
+9  A: 

For coarse-grained security, you might find the inbuilt principal code useful; the user object (and their roles) are controlled in .NET by the "principal", but usefully the runtime itself can enforce this.

The implementation of a principal can be implementation-defined, and you can usually inject your own; for example in WCF.

To see the runtime enforcing coarse access (i.e. which functionality can be accessed, but not limited to which specific data):

static class Roles {
    public const string Administrator = "ADMIN";
}
static class Program {
    static void Main() {
        Thread.CurrentPrincipal = new GenericPrincipal(
            new GenericIdentity("Fred"), new string[] { Roles.Administrator });
        DeleteDatabase(); // fine
        Thread.CurrentPrincipal = new GenericPrincipal(
            new GenericIdentity("Barney"), new string[] { });
        DeleteDatabase(); // boom
    }

    [PrincipalPermission(SecurityAction.Demand, Role = Roles.Administrator)]
    public static void DeleteDatabase()
    {
        Console.WriteLine(
            Thread.CurrentPrincipal.Identity.Name + " has deleted the database...");
    }
}

However, this doesn't help with the fine-grained access (i.e. "Fred can access customer A but not customer B").


Additional; Of course, for fine-grained, you can simply check the required roles at runtime, by checking IsInRole on the principal:

static void EnforceRole(string role)
{
    if (string.IsNullOrEmpty(role)) { return; } // assume anon OK
    IPrincipal principal = Thread.CurrentPrincipal;
    if (principal == null || !principal.IsInRole(role))
    {
        throw new SecurityException("Access denied to role: " + role);
    }
}
public static User GetUser(string id)
{
    User user = Repository.GetUser(id);
    EnforceRole(user.AccessRole);
    return user;
}

You can also write your own principal / identity objects that do lazy tests / caching of the roles, rather than having to know them all up-front:

class CustomPrincipal : IPrincipal, IIdentity
{
    private string cn;
    public CustomPrincipal(string cn)
    {
        if (string.IsNullOrEmpty(cn)) throw new ArgumentNullException("cn");
        this.cn = cn;
    }
    // perhaps not ideal, but serves as an example
    readonly Dictionary<string, bool> roleCache =
        new Dictionary<string, bool>();
    public override string ToString() { return cn; }
    bool IIdentity.IsAuthenticated { get { return true; } }
    string IIdentity.AuthenticationType { get { return "iris scan"; } }
    string IIdentity.Name { get { return cn; } }
    IIdentity IPrincipal.Identity { get { return this; } }

    bool IPrincipal.IsInRole(string role)
    {
        if (string.IsNullOrEmpty(role)) return true; // assume anon OK
        lock (roleCache)
        {
            bool value;
            if (!roleCache.TryGetValue(role, out value)) {
                value = RoleHasAccess(cn, role);
                roleCache.Add(role, value);
            }
            return value;
        }
    }
    private static bool RoleHasAccess(string cn, string role)
    {
        //TODO: talk to your own security store
    }
}
Marc Gravell
+1  A: 

my answer is probably dependent upon the answer to this question: Is this an Enterprise application which lives within a network with Active Directory?

IF the answer is yes, then these are the steps I would provide:

1) Create Global Groups for your application, in my case, I had a APPUSER group and an APPADMIN group.

2) Have your SQL Server be able to be accessed in MIXED AUTHENTICATION mode, and then assign your APPUSER group(s) as the SQL SERVER LOGIN to your database with the appropriate CRUD rights to your DB(s), and ensure that you access the SQL SERVER with Trusted Connection = True in your connection string.

At this point, your AD store will be responsible for authentication. Since, you're accessing the application via a TRUSTED CONNECTION, it will pass the identity of whatever account is running the application to the SQL Server.

Now, for AUTHORIZATION (i.e. telling your application what the logged in user is allowed to do) it's a simple matter of querying AD for a list of groups which the logged in user is a member of. Then check for the appropriate group names and build your UI based upon membership this way.

The way my applications work are thus:

  1. Launching the application, credentials are based upon the logged-in user, this is the primary aspect of authentication (i.e. they can log in therefore they exist)
  2. I Get all Groups For the Windows Identity in question
  3. I check for the Standard USER Group -- if this group does not exist for the Windows Identity in question, then that's an authentication FAIL
  4. I check for ADMIN User Group -- With this existing in the user's groups, I modify the UI to allow access to administration components
  5. Display the UI

I then have either a PRINCIPLE object with the determined rights/etc on it, or I utilize GLOBAL variables that I can access to determine the appropriate UI while building my forms (i.e. if my user is not a member of the ADMIN group, then I'd hide all the DELETE buttons).

Why do I suggest this?

It's a matter of deployment.

It has been my experience that most Enterprise Applications are deployed by Network Engineers rather than programmers--therefore, having Authentication/Authorization to be the responsibility of AD makes sense, as that is where the Network guys go when you discuss Authentication/Authorization.

Additionally, during the creation of new users for the network, a Network Engineer (or whoever is responsible for creating new network users) is more apt to remember to perform group assignments while they are IN AD than the fact that they have to go into a dozen applications to parse out assignments of authorization.

Doing this helps with the maze of permissions and rights that new hires need to be granted or those leaving the company need to be denied and it maintains authentication and authorization in the central repository where it belongs (i.e. in AD @ the Domain Controller level).

Stephen Wrighton
Saajid Ismail
A: 

I think you are looking at a few separate problems here--it is no accident most security systems separate authentication and authorization.

For authentication, the bigger question is logistical. Or, is there a logical place for these users to live, be it locally to the application, in Active Directory, some other LDAP store or even in some other application. Exactly where is pretty immaterial--we just need to be able to solidly identify users and preferably make that task someone else's problem. End of the day you really just need a unique identifier and the comfort that Bob from Accounting is actually Bob from Accounting.

Authorization is the more interesting part of the problem here. I think, if it is truly fine-grained, you really want to manage this wholly within your application, no matter where the users come from. Marc Gravell really hit on a good way to model at least some of this--use some custom implementation of IPrincipal and PrincipalPermission to manage things is a very clean way to get started. Beyond that you can use techniques like this one to make more complex authorization decisions in a rather clean manner.

Wyatt Barnett