Quantcast
Channel: Frans Senden Blog » franssenden
Viewing all articles
Browse latest Browse all 10

Custom Security OData Service – Wcf Data Services

$
0
0

I ran into an authentication/authorization make-a-decision-issue, about how to provide custom access to an OData Service. Also, I wanted this to work through “single” permissions for each user. What I mean by this is not to give users the final permissions through roles, but more directly, so I could authorize users consuming  the service  in a more flexible manner.
For the authentication-part: There are many ways to do this.  Please read an interesting article from Mike Taulty on this topic.

Authentication
Fact: You could use asp.net form based authentication, windows authentication etc.. This for me wasn’t an option, because of the service’s “open” character. One would bother customers having a complex implementation time; assembling cookies, firing exotic client tools or spent hours on learning the-microsoft-way when they’re not used to it (which isn’t a bad thing of course, but let’s respect our fellow developers :-) ) Although, I wanted to leave the MembershipProvider -‘pattern’ in, cause this comes in very handy.

I descided to go for a SSL/Basic Authentication solution, which is absolute secure!! (There are other discussions on this, which I leave out for now). Let’s talk about Basic Authentication for one minute:

On an Http-Request, Before transmission, the user name is appended with a colon and concatenated with the password. The resulting string is encoded with the Base64 algorithm. For example, given the user name Aladdin and password open sesame, the string Aladdin:open sesame is Base64 encoded, resulting in QWxhZGRpbjpvcGVuIHNlc2FtZQ==. The Base64-encoded string is transmitted and decoded by the receiver, resulting in the colon-separated user name and password string.

First thing we have to do is design some kind of a mechanism to handle requests containing these credentials and authenticate our user through a custom MembershipProvider.
Let’s start by creating a new class implementing the IHttpModule:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Security.Principal;
using NL.ADA.TNT.CustomService.Modules;
using Ada.Cdf.Logging;

namespace NL.ADA.TNT.CustomService.Modules
{
    public class ODataServiceModule : IHttpModule
    {

        private CustomMembershipProvider membershipProvider;
        private Logger _logger;

        public ODataServiceModule()
        {
            _logger = new Logger();
        }

        #region IHttpModule Members

        public void Dispose()
        {
            _logger.Log(LogEvent.EnterEvent);
            membershipProvider = null;
            _logger.Log(LogEvent.LeaveEvent);
        }

        public void Init(HttpApplication
{
            membershipProvider = (CustomMembershipProvider)Membership.Provider;
            context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);
            context.AuthorizeRequest += new EventHandler(context_AuthorizeRequest);
            context.BeginRequest += new EventHandler(context_BeginRequest);
        }

        void context_BeginRequest(object sender, EventArgs e)
        {
                   HttpApplication context = sender as HttpApplication;
            if (context.User == null)
            {
                if (!TryAuthenticate(context))
                {
                    SendAuthHeader(context);
                    return;
                }

            }

        }

        void context_AuthorizeRequest(object sender, EventArgs e)
        {
		//insert custom code
        }

        void context_AuthenticateRequest(object sender, EventArgs e)
        {

            //TODO: rules?
            HttpApplication context = sender as HttpApplication;
            TryAuthenticate(context);

        }

        #endregion

        private void SendAuthHeader(HttpApplication context)
        {
            context.Response.Clear();
            context.Response.StatusCode = 401;
            context.Response.StatusDescription = "Unauthorized";
            context.Response.AddHeader("WWW-Authenticate", "Basic realm=\"Secure Area\"");
            context.Response.Write("401 please authenticate");
            context.Response.End();

        }

        private bool TryAuthenticate(HttpApplication context)
        {

            string authHeader = context.Request.Headers["Authorization"];

            if (!string.IsNullOrEmpty(authHeader))
            {
                if (authHeader.StartsWith("basic ", StringComparison.InvariantCultureIgnoreCase))
                {

                    string userNameAndPassword = Encoding.Default.GetString(
                        Convert.FromBase64String(authHeader.Substring(6)));
                    string[] parts = userNameAndPassword.Split(':');

                    if (membershipProvider.ValidateUser(parts[0], parts[1]))
                    {
                        var userIdentity = new GenericIdentity(parts[0].Trim(), "Basic");
                        context.Context.User = new CustomServicePrincipal(userIdentity);

                        return true;
                    }
               }
            }

            return false;
        }
    }
}

As you can see, we create a new Principal in this line:

context.Context.User = new CustomServicePrincipal(userIdentity);

I’ll come back to this in a minute. First you’ll have to notice this is a custom module that enables you to handle basic-authentication in IIS (7.5).
Next we’ve to register this as a custom module in IIS…

Add Custom Module In IIS

….and disable all authentication-options, except anonymous:

disable authentication iis

For now our module is set and intercepts each request doing our custom thing.

Authorization

As I mentioned earlier, for the actual authorization purpose we create a custom principal. that is a new Class that inherits GenericPrincipal. By doing this we’re able to “write” our user to the HttpContext and can call it’s UserPermissionSet property anytime:

   public class CustomServicePrincipal : GenericPrincipal
    {
        private IIdentity _identity;
        private MyEntities _context;

        public CustomServicePrincipal(IIdentity identity)
            : base(identity, new string[] { })
        {

            _context = new MyEntities();
            _identity = identity;
            UserPermissionSet = GetUserPermissionSet();
        }

        public Permission UserPermissionSet { get; set; }
        private Permission GetUserPermissionSet()
        {
            try
            {
                var query = from up in _context.UserPermissions
                            where up.User.UserName == this.Identity.Name
                            select up.Permission;

                Permission flags = 0;
                foreach (var userPermission in query)
                {
                    flags |= (Permission)userPermission.ID;
                }
                return flags;

            }
            catch (Exception ex)
            {

                throw new Exception(string.Format("could not get permissionset for current user{0}", this.Identity.Name), ex.InnerException);
            }

        }

     }

Please note: the permissionset is received from the database (thru entity-framework 4.0). If you want to authorize methods, properties and so on, the thing that worked for me is to handle those permissions as bitwise “flags” so that later on you can compare very easily throughout all the application. To make this clear, here’s an example:

   [Flags]
    public enum Permission
    {
        userMayReadBedtimeStories = 1,
        userMayReadHorrorStories = 2,
        userMayReadHumoristicStories = 4,
        userMayReadNewsStories = 8,
        userMayReadOtherKindoStories = 16,
        userMayPostStory = 32

    }

 

At the persistance-level I stored these permissions in a “permission”-table which relates to table “User”. Username and Password are stored here also. Permissions for each user we’ll get from the “UserPermission”-crosstable.

Now we’re ready to demand some of these permissions on a method:

        [ChangeInterceptor("Stories")]
        public void OnChangeStory(Story story, UpdateOperations updateOperations)
        {
                      AuthDemand(Permission.userMayPostStory);
//add a story
.
.
.
}
        private void AuthDemand(Permission demandPermissions)
        {
            var userPermissionSet = (HttpContext.Current.User as CustomServicePrincipal).UserPermissionSet;
            if ((userPermissionSet & demandPermissions) != (demandPermissions))
            {
                throw new Exception("no permission");//put whatever you want to throw here
            }
        }

thank you also:
Bjorns Blog



Viewing all articles
Browse latest Browse all 10

Trending Articles