[PEAK] peak.security

Phillip J. Eby pje at telecommunity.com
Tue Oct 21 17:47:13 EDT 2003

At 02:03 PM 10/21/03 -0700, John Landahl wrote:
>Again for another "off topic" topic...
>peak.security looks very promising, but I haven't quite been able to wrap 
>my head around it yet from just the unit tests.  Are any examples of its 
>use planned?
>What I'm hoping to do is secure object interactions at the method and 
>attribute level, so that one object accessing another on behalf of a user 
>will encounter exceptions where the user is not permitted access.  More 
>specifically, a Twisted Perspective object P will attempt to access a PEAK 
>component C on behalf of a remote user U who belongs to a "role" R (our 
>idea of a "role" seems to match up with security.Permission).  If C's 
>method M1 does not allow role R, then P calling C.M1 should generate an 
>Using peak.security, how would I disallow R or U from calling C.M1?  Would 
>I call C.M1 normally from P (and get the exception as expected), or would 
>accessing C's attributes/methods have to go through some sort of accessor 
>function?  How would I define that U and R are the active permissions when 
>making the method call attempt?

peak.security does not implement proxy wrappers for objects, so you have to 
create your own accessor function or proxy.  You will need a 
security.IInteraction object to check the permissions with (using its 
'allows()' method); the default security.Interaction class will probably 
suffice.  The interaction holds the user, and a security context in the 
form of a protocol.  (I'll get back to the context/protocol part later.)

To specify what permission something has, you can do it on a binding (e.g. 
binding.Make(something, permissionNeeded=FooPermission)), or as a function 
attribute (e.g. somefunc.permissionNeeded = FooPermission).  If all else 
fails, you can use 'security.allow()' in a class body to mark permissions 
needed for attributes.

The 'allows()' method of IInteraction is what does the permission 
checking.  This works by adapting the abstract permission 
(permissionNeeded) to rules for each concrete permission.  Both the module 
and the unit tests give examples of rule classes.  These are basically 
classes that have methods to verify that a user has the permission for that 
object.  Normally, a rule class derives from class security.RuleSet, e.g.:

class EquipmentRules(security.RuleSet):

     rules = Items(
         checkWorkerForShipment = [Worker.of(Shipment)],
         checkSupervisor = [Manager.of(Person)],
         checkSelfOrManager = [SelfOrManager],
         checkWorkerOrManager = [ManageAsset, ShipmentViewer],
         checkManageBatch = [ManageBatch],
         checkPermissionsInPlace = [Worker, Manager],

This example (from the unit tests) says that the 'checkWorkerForShipment()' 
method of this class should be called to check whether a user has the 
abstract permission Worker, in relation to an instance of class 
Shipment.  The expression 'Worker.of(Shipment)' is a *concrete permission*, 
whereas just 'Worker' is an abstract permission.  You use abstract 
permissions when defining permissionNeeded, but you can use abstract or 
concrete permissions when defining security rules.  (Btw, to create an 
abstract permission, just create an empty subclass of 
'security.Permisson'.  That's all.)

Rules are implemented as classmethods of a ruleset class, e.g.:

     def checkWorkerForShipment(klass, attempt):
         return attempt.allows(attempt.subject.fromFacility,
         ) or   attempt.allows(attempt.subject.toFacility,
         ) or security.Denial(
             "You need to be a worker at either the origin or destination"
             " facility for this shipment."

You do not need to declare them as class methods; this is done 
automatically by RuleSet's metaclass.  But your first argument is the 
ruleset class, and your second argument is a security.IAccessAttempt 
containing all the details of the access being attempted.  The method 
should return a true value if permission is granted, otherwise it may 
return any false value.  However, the most useful false value to return is 
a 'security.Denial()' as shown above.  This allows the security machinery 
to pass information back to a UI to optionally give to the 
user.  ('security.Denial' instances are always false in the boolean sense, 
regardless of the message content.)

The above example shows a common security rule scenario: how to make a 
permission that is based on other permissions.  In this case the rule is 
that you can have the 'Worker' permission for a Shipment if you are a 
Worker at either the origin or destination facility, otherwise, you receive 
a Denial explaining the circumstances.  This also illustrates the use of 
the 'allows()' method on an IAccessAttempt, in order to do another access 
check that changes only some of the parameters of the current attempt.  In 
this case, we use 'attempt.subject' to get at the Shipment we're checking 
access to, and then the Shipment's fromFacility or toFacility.  We then 
check whether the user has Worker permission on either of those facilities, 
using attempt.allows().  Check out the IAccessAttempt source for more info 
on the API.

Sometimes you may want to base a permission on an AND-combination of other 
permissions, rather than or.  Obviously, this works the same way, 
substituting 'and' between the delegated permissions, but you still need to 
structure the overall expression as 'attempt.allows(...) and 
attempt.allows(...) or security.Denial(...)'.  That is, the Denial should 
be in an 'or' clause so that it takes effect when the other expressions 
fail.  Alternately, you can leave off the 'or Denial' clause, in which case 
any denial from the individual items will pass through to the parent rule 
or access attempt.

When you create a RuleSet, you need to declare it.  You'll notice that in 
the tests and elsewhere, each RuleSet class is followed by, e.g. 
'Universals.declareRulesFor(IPermissionChecker)'.  You might be wondering 
what this is for.  Well, a RuleSet by itself is just a ruleset, and doesn't 
*do* anything.  An Interaction needs to know what RuleSet(s) apply to that 
interaction.  You may remember me saying earlier that an interaction has a 
context in the form of a protocol.  That's how it knows what ruleset(s) to 
use: it uses the rulesets that are declared for its 'permissionProtocol', 
or any protocol that implies the permissionProtocol.

Confused yet?  Let's back up a bit.  Why do we need different 
RuleSets?  Because RuleSets implement business rules, not core domain 
behavior.  Core application domain behavior tells us only *abstract* 
permission groupings that exist in the application domain as groupings of 
function.  But, how those permissions should be applied to users is a 
business rule area.  Thus, we can't have the user-permission-object mapping 
embedded in the core domain classes, which means there can be more than one 
mapping, which means we need to be able to say what set of rules we're using.

But, what happens if we want to supply a set of "default" rules, and then 
let somebody override them?  We don't want them to have to rewrite all the 
rules just to add a few.  So, we use a protocol.  The 'permissionProtocol' 
of an interaction is the protocol that permission objects will be adapted 
to, in order to get an implementation of the permission.  By default, the 
permissionProtocol is security.IPermissionChecker, however you can create 
variations of this protocol (as the unit tests do) that will "inherit" any 
rules defined for IPermissionChecker.  In this way, you can "inherit" from 
an existing set of RuleSets, and then declare only new RuleSets for your 
new protocol.  (You can also have new RuleSets that subclass old RuleSets 
and override things, but they can't be declared for the same 
permissionProtocol, or the definitions will conflict.)

Okay, now that you're completely confused, here's the simple rule of 
thumb.  When in doubt, just declare your rulesets for IPermissionChecker, 
e.g. 'MyRuleSet.declareRulesFor(IPermissionChecker)'.  If somebody wants to 
use different rules with your application, however, they will need to 
replace your Interaction object's permissionProtocol with a replacement 
interface, and then 'declareRulesFor()' that replacement interface.  So, 
try to design your system such that it's easy for somebody to replace 
either the Interaction class or to configure its instances with a custom 
permissionProtocol.  Then, it will at least be possible for them to change 
your security rules.

More information about the PEAK mailing list