Sample of non-CAS custom permission with declarative form supported.

Why?

Recently, I started seeing numerous requests regarding creation of custom permissions that do not inherit from CodeAccessPermission and thus do not perform stackwalk. There is nothing special about implementing such classes. In fact, it is easier then with CodeAccessPermission as a base. However, having a sample handy, I just decided to share it here along with my comments. So welcome

WorkingTimePermission

Trying to be at least somewhat close to real life, I implemented the permission object with the following Demand semantics:

If Demand is performed during business hours, it passes.

If it is done in other time, it throws the SecurityException.

As you see, it’s straightforward. However, it might save you a good chunk of time when you use it declaratively, e.g.:

[method:WorkingTimePermissionAttribute(SecurityAction.Demand)]

private static void AccessibleDuringBusinessHoursOnly()

{

            //…

}

…instead of performing time checks explicitly on each protected function entrance.

The sample consists of 4 files: Permission DLL, Permission Attribute DLL, Client file that uses it and script that builds all together. My comments are in gray. Catches or important places are in dark red. Enjoy!

1. WorkingTimePermission.cs:

using System;

using System.Security;

using System.Security.Permissions;

// Make sure the assembly is signed; otherwise it will fail some of the actions needed for build sequence.

[assembly:System.Reflection.AssemblyKeyFile("SomeKey.snk")]

[assembly:System.Security.AllowPartiallyTrustedCallersAttribute()]

namespace CustomPermissions

{

            [Flags,Serializable]

            public enum AccessType

            {

                        Common = 0x00,

                        VIP = 0x01

            }

            public sealed class WorkingTimePermission : IPermission

            {

                        /* I’ve implemented this permission having two access

                        modes just to show how the case of permission having

                        internal data should be dealt with. But in fact this code

                        could be simplified even further by removing these flags */

                        private AccessType m_Access;

                        public WorkingTimePermission()

                        {

                                   m_Access = AccessType.Common;

                        }

                        public WorkingTimePermission(AccessType access)

                        {

                                   VerifyAccess(access);

                                   m_Access = access;

                        }

                        /* Catch #1: although IPermission does not obligate you

                        to implement this kind of constructor, it must be specified

                        if you wish to have the declarative form of the permission,

                        because Security system will silently try to use it when decoding

                        the attribute, and fail if it's not found. */

                        public WorkingTimePermission(PermissionState State)

                        {

                                   m_Access = AccessType.Common;

                        }

                        private void VerifyAccess(AccessType access)

                        {

                                   if (0 != (((int) access) & ~1))

throw new ArgumentException("Wrong access type value: " + (int) access);

                        }

                        // This is required by IPermission interface.

                        public IPermission Copy()

                        {

                                   return new WorkingTimePermission(this.m_Access);

                        }

                        private void VerifyType(IPermission Target, bool IsNullOK)

                        {

                                   if (null == Target)

                                   {

                                               if (true == IsNullOK) return;

                                               else throw new ArgumentException("Target is not WorkingTimePermission [it is null]");

                                   }

                                   if (this.GetType() != Target.GetType())

                                   throw new ArgumentException("Target is not WorkingTimePermission");

                        }

                        public AccessType GetAccess()

                        {

                                   return m_Access;

                        }

                        // This is required by IPermission interface.

                        public IPermission Intersect(IPermission Target)

                        {

                                   if (null == Target) return null;

                                   VerifyType(Target, false);

                                   WorkingTimePermission P = (WorkingTimePermission) Target;

                                   return new WorkingTimePermission(this.GetAccess() & P.GetAccess());

                        }

                        // This is required by IPermission interface.

                        public IPermission Union(IPermission Target)

                        {

                                   VerifyType(Target, true);

                                   WorkingTimePermission P = (WorkingTimePermission) Target;

                                   return new WorkingTimePermission(this.GetAccess() | P.GetAccess());

                        }

                        // This is required by IPermission interface, too

                        /* Catch #2: if you work with V1.0 or V1.1, this method will be silently

                        called by the compiler during build time if permission is used

                        declaratively. If any error occurs inside the body of this method, it is not

                        propagated to the above, issuing instead quite a bogus message like

                        “failed to create the permission for this attribute”. If it sounds familiar,

                        double your attention to this place: inject more debugging output, etc.*/

                        public bool IsSubsetOf(IPermission Target)

                        {

                                   VerifyType(Target, true);

                                   if (null == Target) return false;

                                   WorkingTimePermission P = (WorkingTimePermission) Target;

                                   return (this.GetAccess() <= P.GetAccess());

                        }

                        /* This is the core logics part of the permission. I’ve implemented it this way:

                        For VIP access, Demand always passes.

                        For normal access, it passes during “usual” business hours only.

                        Of course, you are more then welcome to implement whatever logics you see suitable.

                        Note please that all exception messages are hardcoded in English here.

                        This is not a good style in general, and done only for the sake of simplicity. */

                        public void Demand()

                        {

                                   Console.WriteLine("WorkingTimePermission.Demand() called!");

                                   if (AccessType.VIP == this.GetAccess()) return;

                                   DateTime Curr = DateTime.Now;

                                   DayOfWeek CurrDay = Curr.DayOfWeek;

                                   if ((CurrDay == DayOfWeek.Saturday)||(CurrDay == DayOfWeek.Sunday))

                                               throw new SecurityException("Request for WorkingTimePermission failed because current day of week is " + CurrDay + ".");

                                   int Hour = Curr.Hour;

                                   if ((Hour < 8)||(Hour > 17))

                                               throw new SecurityException("Request for WorkingTimePermission failed because current time is " + Hour + " hours.");

                        }

                        // This is required by IPermission interface.

                        public SecurityElement ToXml()

                        {

                                   SecurityElement Ret = new SecurityElement("IPermission");

                                   String name = typeof(WorkingTimePermission).AssemblyQualifiedName;

                                   Ret.AddAttribute("class", name);

                                   Ret.AddAttribute("version", "1.0");

                                   Ret.AddAttribute("AccessType", this.GetAccess().ToString());

                                   return Ret;

                        }

                        // This is required by IPermission interface.

                        public void FromXml(SecurityElement e)

                        {

                                   String name = e.Attribute("class");

                                   // Make sure we are not converting “something else” to WorkingTimePermission:

                                   if (name != typeof(WorkingTimePermission).AssemblyQualifiedName) throw new ArgumentException("Wrong SecurityElement");

           

                                   String version = e.Attribute("version");

                                   if (version != "1.0") throw new ArgumentException("Version " + version + " does not match current version of the permission");

                                   String access = e.Attribute("AccessType");

   

                                   if (null != access) m_Access = (AccessType) Enum.Parse(typeof(AccessType), access);

                                   else m_Access = AccessType.Common;

                        }

            }

}

2. WorkingTimePermissionAttribute.cs:

using System;

using System.Security;

using System.Security.Permissions;

[assembly:System.Reflection.AssemblyKeyFile("SomeKey.snk")]

[assembly:System.Security.AllowPartiallyTrustedCallersAttribute()]

namespace CustomPermissions

{

            [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false )]

            /*Catch #3: use CodeAccessSecurityAttribute as a base class, don’t be tempted by SecurityAttribute. */

            public sealed class WorkingTimePermissionAttribute : CodeAccessSecurityAttribute

            {

                        private AccessType m_Access;

                        public WorkingTimePermissionAttribute(SecurityAction action): base(action)

                        {

                                   m_Access = AccessType.Common;

                        }

                        // This is required by CodeAccessSecurityAttribute:

            public override IPermission CreatePermission()

                        {

                                   return new WorkingTimePermission(m_Access);

                        }

                        /* Catch #4: Even if permission has some internal parameter named Data,

                        we don't need to implement constructor(DataType Data). Attributes think that

                        Data is a property of the permission, so let’s implement it as a property: */

                        public AccessType Access

                        {

                                   get

                                   {

                                               return m_Access;

                                   }

                                   set

                                   {

                                               m_Access = value;

                                   }

                        }

            }

}

3. Client.cs

This is the sample of the code that uses WorkingTimePermission:

using System;

using System.Security;

using System.Security.Permissions;

using CustomPermissions;

// This class is created only to show the inheritance protection by the new permission:

public class BBB : AAA

{

            public override void Ozz()

            {

                        Console.WriteLine("BBB.Ozz() being called");

            }

}

// This is the second, and the main class, containing all the demo code.

// It consists of several methods and calls to them, nothing more:

public class AAA

{

            /* This is the demo of explicit permission usage.

            Demand will pass during business hours and fail otherwise. */

            private static void Foo()

            {

                        WorkingTimePermission P = new WorkingTimePermission();

                        P.Demand();

                        Console.WriteLine("AAA.Foo() called!");

            }

            /* Here is the same thing as above, but used declaratively.

            The attribute ensures that method Bar() could be called during business time only.

            Isn’t it neat? :) */

            [method:WorkingTimePermissionAttribute(SecurityAction.Demand)]

            private static void Bar()

            {

                        Console.WriteLine("AAA.Bar() called!");

            }

            /*This is only to show how to use attribute with the property. It is a plain

            syntax demo, because our permission is implemented in such a way that VIP

            demand always passes thus making the permission useless. However, we

            can code some different schedule for VIPs, too */

            [method:WorkingTimePermissionAttribute(SecurityAction.Demand, Access = AccessType.VIP)]

            private static void BarVIP()

            {

                        Console.WriteLine("AAA.BarVIP() called!");

            }

            /* Here you go: we’ve implemented Demand only, but got LinkDemand for free, too.

            And it works, ensuring that Rug() method cannot be even instantiated during

            non-business hours: */

            [method:WorkingTimePermissionAttribute(SecurityAction.LinkDemand)]

            private static void Rug()

            {

                        Console.WriteLine("AAA.Rug() called!");

            }

            /* Catch #5: if you want to be able to see the actual SecurityException from the method

            protected by JIT-time action like LinkDemand, wrap a target method into another one

            and call the wrapper from inside a try...catch block. Without a wrapper, SecurityException

            will be thrown during JIT-time, when try...catch does not exist yet, thus going uncaught.

            So the sample below calls RugWrapper() instead of just Rug().*/

            private static void RugWrapper()

            {

                        Rug();

            }

            // And, yes, InheritanceDemand just works, too!

            // So, see, everyone who inherited from your AAA

            // class will loose the access to Ozz during non-business hours.

            [method:WorkingTimePermissionAttribute(SecurityAction.InheritanceDemand)]

            public virtual void Ozz()

            {

                        Console.WriteLine("AAA.Ozz() being called");

            }

            // Same notes about JIT-time actions as in RugWrapper() apply:

            private static void BBBOzzWrapper()

            {

                        (new BBB()).Ozz();

            }

            public static void Main()

            {

                        try

                        {

                                   Foo();

                        }

                        catch (SecurityException e)

                        {

                                   Console.WriteLine(e);

                        }

                        try

                        {

                                   Bar();

                        }

                        catch (SecurityException e)

                        {

                                   Console.WriteLine(e);

                        }

                        try

                        {

                                   BarVIP();

                        }

                        catch (SecurityException e)

                        {

                                   Console.WriteLine(e);

                        }

                        try

                        {

                                   RugWrapper();

                        }

                        catch (SecurityException e)

                        {

                                   Console.WriteLine(e);

                        }

                        try

                        {

                                   BBBOzzWrapper();

                        }

                        catch (SecurityException e)

                        {

                                   Console.WriteLine(e);

                        }

            }

}

4. Build script. I assumed that all the tools like caspol.exe are on the path:

REM reset the policy, remove the garbage, clean the GAC, so we won’t stumble over the previously broken build :)

caspol -pp off -all –reset

del *.dll

del *.exe

gacutil /u WorkingTimePermission

gacutil /u WorkingTimePermissionAttribute

REM Create a StrongName key [or copy it from somewhere…]:

sn -k SomeKey.snk

REM Build the permission DLL:

csc /debug+ /t:library WorkingTimePermission.cs

REM: Now is the important detail: in order to work properly, every DLL that implements classes used by Security must be in the GAC and must be in the list of fully trusted assemblies, so let’s do it:

gacutil /i WorkingTimePermission.dll

caspol -af WorkingTimePermission.dll

REM: building attribute…

csc /debug+ /t:library /r:WorkingTimePermission.dll WorkingTimePermissionAttribute.cs

gacutil /i WorkingTimePermissionAttribute.dll

caspol -af WorkingTimePermissionAttribute.dll

REM: …and the client.

csc /debug+ /r:WorkingTimePermission.dll /r:WorkingTimePermissionAttribute.dll Client.cs

REM: it's done!

That’s it!

Other uses of permission classes?

Just as a quick side note… If you try to think of permission as of “deferred condition” object, you might find a number of interesting uses for custom classes like the above. Examples include:

CallDepthPermission: demand passes if the length of the callstack is less then some particular number [say, passed as a parameter].

OSLanguagePermission: you want your code to be able to run on a limited set of OS languages only? It’s very easy to implement!

NetworkStatusPermission: how about an attribute that allows function to be called only when the machine is off the network?

Have fun!

 

Important update for users of .NET Framework 2.0 ["Whidbey"]

 

Due to the number of Permission semantics changes, the above sample will not work as described in 2.0 if run in Full Trust environment. Partial trust behavior should remain unchanged though. More details are available at https://blogs.msdn.com/eugene_bobukh/archive/2005/05/06/415217.aspx .