Search code examples
c#wcf

Check user access with custom attributes


Having user information in a Session, how possible is to check the session and allow access to the method decorated with the custom attribute based on one of the values provided.

So what I'm trying to do is:

public class UserAccess: System.Attribute
{
    private string userRole;   

    public UserAccess(string userRole)
    {
        this.userRole = userRole;

    }
}

Then when I decorate an endpoint like this:

[UserAccess(userRole = "Residents")]
public Response Get(Request r){
    ///-- Implementation
}

Somehow when the endpoint is invoked only userRole = "Residents" can actually execute it based on a session value check up. Also, can this validation be done in the custom attribute implementation?


Solution

  • So the other guys are right, that the attributes do nothing by themselves. It is just metadata that you have to purposely get at some point during the lifetime of the service call.

    The best way to do that so it is sort of done auto-magically and not always directly in every operation is to add inspectors and service behaviors. It is more work to setup initially, but it gets that out of your direct operation code and can make it apply for any operation to check for that custom attribute.

    Basically you have your attribute like so:

    namespace MyCustomExtensionService
    {
        public class UserAccessAttribute : System.Attribute
        {
            private string _userRole;
    
            public UserAccessAttribute(string userRole)
            {
                _userRole = userRole;
                
                //you could also put your role validation code in here
                
            }
    
            public string GetUserRole()
            {
                return _userRole;
            }
        }
    }
    

    Then you set up your parameter inspector (note there are other inspectors you could use):

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    using System.ServiceModel.Dispatcher;
    using System.Web;
    
    namespace MyCustomExtensionService
    {
        public class MyParameterInspector : IParameterInspector
        {
    
            public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
            {
                //throw new NotImplementedException();
            }
    
            public object BeforeCall(string operationName, object[] inputs)
            {
                MethodInfo method = typeof(Service1).GetMethod(operationName);
                Attribute[] attributes = Attribute.GetCustomAttributes(method, typeof(UserAccessAttribute), true);
    
                var attr = (UserAccessAttribute)attributes.First();
    
                if (attributes.Any())
                {
                    var userHasProperAuthorization = true;
                    if (attr.GetUserRole() == "Residents" && userHasProperAuthorization)
                    {
                        //everything is good, continue to operation
                    }
                    else
                    {
                        throw new FaultException("You do not have the right security role!");
                    }
                }
    
                
    
                return null;
    
            }
        }
    }
    

    Then you setup your endpoint behavior (there are other behaviors you could use):

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.ServiceModel.Description;
    using System.ServiceModel.Dispatcher;
    using System.Web;
    
    namespace MyCustomExtensionService
    {
        public class MyCustomAttributeBehavior : IEndpointBehavior
        {
            public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
            {
                //throw new NotImplementedException();
            }
    
            public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
            {
                foreach (ClientOperation clientOperation in clientRuntime.Operations)
                {
                    clientOperation.ParameterInspectors.Add(
                        new MyParameterInspector());
                }
            }
    
            public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
            {
                foreach (DispatchOperation dispatchOperation in endpointDispatcher.DispatchRuntime.Operations)
                {
    
                    dispatchOperation.ParameterInspectors.Add(
                        new MyParameterInspector());
                }
            }
    
            public void Validate(ServiceEndpoint endpoint)
            {
                //throw new NotImplementedException();
            }
        }
    }
    

    Then you create your behavior section:

    using System.Linq;
    using System.ServiceModel.Configuration;
    using System.Web;
    
    namespace MyCustomExtensionService
    {
        public class MyBehaviorSection : BehaviorExtensionElement
        {
    
            protected override object CreateBehavior()
            {
                return new MyCustomAttributeBehavior();
    
            }
    
            public override Type BehaviorType
            {
    
                get { return typeof(MyCustomAttributeBehavior); }
    
    
            }
        }
    }
    

    Then you setup the config to use the new behavior:

    <system.serviceModel>
        <services>
          <service name ="MyCustomExtensionService.Service1">
            <endpoint address="" behaviorConfiguration="MyCustomAttributeBehavior"
              binding="basicHttpBinding" contract="MyCustomExtensionService.IService1">
            </endpoint>
          </service>
        </services>
        <extensions>
          <behaviorExtensions>
            <add name="Validator" type="MyCustomExtensionService.MyBehaviorSection, MyCustomExtensionService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          </behaviorExtensions>
        </extensions>
        <behaviors>
          <endpointBehaviors>
            <behavior name="MyCustomAttributeBehavior">
              <Validator />
            </behavior>
          </endpointBehaviors>
    

    here is the services interface - with one operation that will work and one that will fail due to having the wrong user access

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.ServiceModel.Web;
    using System.Text;
    
    namespace MyCustomExtensionService
    {
        
        [ServiceContract]
        public interface IService1
        {
    
            [OperationContract]
            string GetData(int value);
    
            [OperationContract]
            string GetDataUsingWrongUserAccess(int value);
    
        }
    
    
       
    }
    

    And the service operations:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.ServiceModel.Web;
    using System.Text;
    
    namespace MyCustomExtensionService
    {
       
        public class Service1 : IService1
        {
            [UserAccess("Residents")]
            public string GetData(int value)
            {
                return string.Format("You entered: {0}", value);
            }
    
            [UserAccess("Admin")]
            public string GetDataUsingWrongUserAccess(int value)
            {
                return string.Format("You entered: {0}", value);
            }
        }
    }
    

    For more info, see MSDN http://msdn.microsoft.com/en-us/library/ms730137.aspx

    also for inspectors:https://github.com/geersch/WcfParameterInspectors