Sayfalar

9 Mart 2015 Pazartesi

Tasarım Desenleri-3

Tasarım Desenleri-3

Proxy Pattern İle İzin Yönetimi

Bu blogta transparent proxy ile method bazlı yetkilendirmenin nasıl yapılabileceğini anlatacağım. Öncelikle problemimizden bahsetmek istiyorum daha sonrada çözüm yollarını araştıracağız. İş katmanında yazılan kodlara yetki kontrolü eklemek istiyorum. Bunun için her metoda tek tek girip sen şu roller için çalışacaksın demek istemiyorum. Fakat methodların hangi rollerde olduğuna dair bilgi üzerindeki "attributeler" de mevcuttur. Benim yapmam gereken bu methodlar çağrılmadan önce yetki kontrolü yapmak, eğer yetkisiz erişim mevcutsa hata dönderip methodu çalıştırmamak. Yetki kontrolünü tek bir merkezden yapmak istiyorum. Kısacası  iş katmanı sınıflarındaki methodların önüne geçip yetki kontrolu yapmak bizim problemimiz.

Bu problemi çözmek için reflection kullanılabilir, fakat zaten bunu bizim yerimize yapan bir sınıf var. RealProxy sınıfı MarshalByRefObject sınıfından miras alınarak oluşturulmuş sınıfları açabiliyor ve bize gerekli kodlamayı eklememize izin veriyor. Sonunda o sınıf gibi fakat kodlarımızın eklendiği bir sınıf dönderebiliyoruz. Buna microsoft TransparentProxy adını koymuş.

Öncelikle herhangi bir sınıfı içine alıp TransparentProxy (aynı sınıfın düzenlenmişini) sini dönderen generic bir method yazalım.

public class GenericProxyBuilder<TProxyObj> : RealProxy where TProxyObj : new()
    {
        TProxyObj proxyObj;
        public GenericProxyBuilder(TProxyObj proxyObj)
            : base(typeof(TProxyObj))
        {
            this.proxyObj = proxyObj;
        }
        public GenericProxyBuilder() { }
        public static TProxyObj GetInstance()
        {
            return (TProxyObj)new GenericProxyBuilder<TProxyObj>(new TProxyObj()).GetTransparentProxy();
        }
        public override IMessage Invoke(IMessage msg)
        {
            IMethodCallMessage mcm = (IMethodCallMessage)msg;
            IMessage rtnMsg = null;
            if (mcm != null)
            {
                rtnMsg = new ReturnMessage(mcm.MethodBase.Invoke(proxyObj, mcm.InArgs), null, 0, mcm.LogicalCallContext, mcm);
            }
            return rtnMsg;
        }

    }

Bu sınıf tip olarak içine aldığı sınıftan TransparentProxy oluşturuyor. Abstract olan Invoke metodu ile o sınıfa ait tüm methodların arasına girmiş olacağız. Şimdi de örnek bir iş katmanı sınıfı yazalım. Bu sınıf  MarshalByRefObject  sınıfından miras almak zorundadır.

public  class BussinessLayerCodes:MarshalByRefObject
    {
       public void Method1()
       {
       }
    }

Şimdi de bu kodu çalıştıralım.

class Program
    {
        static void Main()
        {
            var instance = GenericProxyBuilder<BussinessLayerCodes>.GetInstance();
            instance.Method1( );
        }
    }


Bu kodu çalıştırdığımızda "Araya girdik." çıktısından sonra kodumuzun çalıştığını göreceksiniz. Şimdi bu özelliği izin yönetiminide nasıl kullanacağız onu gösterecem.
Öncelikle bir tane attribute tanımlayıp Method1'in üzerine koyup bu methodu işaretleyelim.


  class RoleAttribute:Attribute
  {
      public string Role { get; set; }

  }

public  class BussinessLayerCodes:MarshalByRefObject
    {
       [PermissionAttribute(Role="Manager")]
       public void Method1()
       {
       }
    }

Bizim GenericProxyBuilder sınıfımızı biraz düzenleyerek proxy yapılmış sınıfın metodunun öncesinde ve sonrasında çalışacak methodları parametrik yapalım.

    public class GenericProxyBuilder<TProxyObj> : RealProxy where TProxyObj : new()
    {
        TProxyObj proxyObj;
        public GenericProxyBuilder(TProxyObj proxyObj)
            : base(typeof(TProxyObj))
        {
            this.proxyObj = proxyObj;
        }
        public GenericProxyCreator() { }
        public static TProxyObj GetInstance(Action<MethodBase> after = null, Action<MethodBase> before=null)
        {
            return (TProxyObj)new GenericProxyBuilder<TProxyObj>(new TProxyObj()) { Before = before ?? delegate { }, After = after ?? delegate { } }.GetTransparentProxy();
        }
        private Action<MethodBase> After { get; set; }
        private Action<MethodBase> Before { get; set; }
        public override IMessage Invoke(IMessage msg)
        {
            IMethodCallMessage mcm = (IMethodCallMessage)msg;
            IMessage rtnMsg = null;
            if (mcm != null)
            {
                Before(mcm.MethodBase);
                rtnMsg = new ReturnMessage(mcm.MethodBase.Invoke(proxyObj, mcm.InArgs), null, 0, mcm.LogicalCallContext, mcm);
                After(mcm.MethodBase);
            }
            return rtnMsg;
        }
    }

Daha sonrada transparen proxy ile iş katmanı sınıflarını haberleştirecek generic bir sınıf yazalım.

    public class BussinessCodeCaller<BussinessLayerObject> where BussinessLayerObject : new()
    {
        BussinessLayerObject bussinessObj;
        public BussinessCodeCaller(BussinessLayerObject bussinessObj)
        {
            this.bussinessObj = bussinessObj;
        }
     
        static String[] DemoAuthenticatedUserRoles = { "Manager", "Director" };
        public static void ControlRoles(MethodBase mcm)
        {
            var HasRole = mcm.GetCustomAttributes(true).Where(x => x is RoleAttribute && DemoAuthenticatedUserRoles.Contains(((RoleAttribute)x).Role)).Any();
            if (HasRole == false)
                throw new Exception("Authentication failure!");
        }
        public static void Logging(MethodBase mb)
        {
            //loglama yapılabilir.
        }
        public static BussinessLayerObject Instance
        {
            get
            {

                return GenericProxyBuilder<BussinessLayerObject>.GetInstance(before:ControlRoles,after:Logging);
            }
        }
    }

Yazdığımız kodları test edelim. Aşagıdaki gibi çağıralım. 

  class Program
    {
        static void Main()
        {
         
            var instance = BussinessCodeCaller<BussinessLayerCodes>.Instance;
            instance.Method1();
        }
    }

Yetki hatası vermeyecektir. Şimdide Method1 etiket değerini [PermissionAttribute(Role="Manager")] yerine [PermissionAttribute(Role="Personnel")] ile değiştirelim. Tekrar test ettiğimizde yetki hatasının verdiği görülecektir.