9

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?

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
user1791567
  • 1,388
  • 1
  • 16
  • 29

3 Answers3

18

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

Christophe Geers
  • 8,564
  • 3
  • 37
  • 53
Chris Holwerda
  • 1,255
  • 1
  • 10
  • 20
1

The attributes are mere metadata like flags, description, additional information. You need to process this information yourself. You can do this in the method itself or have some helper class to process it by using reflection.

    // Using reflection.
    MethodInfo method = typeof(ClassName).GetMethod("Get");
    Attribute[] attributes = Attribute.GetCustomAttributes(method, typeof(UserAccess), true);


    // Displaying output. 
    foreach (var attr in attributes)
    {
        if (attr is UserAccess)
        {
            var ua = (UserAccess)attr;
            System.Console.WriteLine("{0}",a.userRole);
        }
    }

* I also suggest to suffix the word Attribute to your UserAccess class as a convention. e.g., UserAccessAttribute

Abbyjeet
  • 89
  • 5
0

No, you cannot do that (not by itself), attributes are nothing more than metadata compiled into your code, by themselves they don't do anything. Once you decorate a method or class with some attribute metadata, you can then use reflection, like GetCustomAttributes(typeof(UserAccess)), to retrieve the metadata and act upon it, this SO answer illustrates this pretty well

What you can do is, create a custom method that will use reflection to retrieve the metadata and do the evaluation for you, then inside public Response Get(Request r), before you do anything you can invoke this method, but this is not exactly the kind of automagic evaluation that you are asking

Community
  • 1
  • 1
Jason
  • 3,844
  • 1
  • 21
  • 40