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 .
Comments
- Anonymous
May 29, 2004
I tried your this tech. I get error "Failure to decode the permission set"
pl. help
sachin. - Anonymous
June 01, 2004
What is the version of .NET you are using? Is it English or not? What is the exact step when you are getting an error?
Typically, such an error is observed when errors happen inside either IsSubsetOf() or To/FromXml() methods. Try adding extra Console.WriteLine() lines into them to track down the point where it happens. - Anonymous
June 22, 2004
Hi. How is possible that inheriting from CASA is the only way for attributes to be supported declaratively? How can I inject my own attribute code without using the CASA? - Anonymous
July 25, 2004
dianying xia zai:http://www.kamun.com/
movie down:http://movie.kamun.com/
mp3 xia zai:http://music.kamun.com/
engage:http://club.kamun.com/ - Anonymous
June 18, 2009
PingBack from http://onlyoutdoorrugs.info/story.php?id=782