[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
>exception.
>
>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,
permissionNeeded=Worker
) or attempt.allows(attempt.subject.toFacility,
permissionNeeded=Worker
) 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