chiark / gitweb /
format.py: Allow general format controls more widely.
[chopwood] / operation.py
index 1184e3d17996f6406ce29bf79c8873291dd76632..9a2d1c0dc60c93f841b12644c81298a242dbbed5 100644 (file)
@@ -40,6 +40,16 @@ import util as U
 ### semantics may be that the services are all assigned the /same/ random
 ### password.)
 
+###--------------------------------------------------------------------------
+### Some utilities.
+
+OPS = ['set', 'reset', 'clear']
+## A list of the available operations.
+
+class polswitch (U.struct):
+  """A small structure holding a value for each operation."""
+  __slots__ = OPS
+
 ###--------------------------------------------------------------------------
 ### Operation protocol.
 
@@ -125,6 +135,12 @@ CONF.export('FailOperation')
 ###--------------------------------------------------------------------------
 ### Requests.
 
+CONF.DEFAULTS.update(
+
+  ## A boolean switch for each operation to tell us whether it's allowed.  By
+  ## default, they all are.
+  ALLOWOP = polswitch(**dict((i, True) for i in OPS)))
+
 ## A request object represents a single user-level operation targetted at
 ## multiple services.  The user might be known under a different alias by
 ## each service, so requests operate on service/user pairs, bundled in an
@@ -158,29 +174,40 @@ class acct (U.struct):
 
 class BaseRequest (object):
   """
-  Base class for requests, provides basic protocol.  In particular, it
-  provides an empty `INFO' map, a trivial `check' method, and the obvious
-  `perform' method which assumes that the `ops' list has already been
-  constructed.
+  Base class for requests, provides basic protocol.
+
+  It provides an empty `INFO' map; a simple `check' method which checks the
+  operation name (in the class attribute `OP') against the configured policy
+  `CFG'ALLOWOP'; and the obvious `perform' method which assumes that the
+  `ops' list has already been constructed.
   """
+
   INFO = {}
+  ## A dictionary describing the additional information returned by the
+  ## request: it maps attribute names to human-readable descriptions.
+
   def check(me):
     """
     Check the request to make sure we actually want to proceed.
     """
-    pass
+    if not getattr(CFG.ALLOWOP, me.OP):
+      raise U.ExpectedError, \
+          (401, "Operation `%s' forbidden by policy" % me.OP)
+
   def makeop(me, optype, svc, user, **kw):
     """
     Hook for making operations.  A policy class can substitute a
     `FailOperation' to partially disallow a request.
     """
     return optype(svc, user, **kw)
+
   def perform(me):
     """
     Perform the queued-up operations.
     """
     for op in me.ops: op.perform()
     return me.ops
+
 CONF.export('BaseRequest', ExpectedError = U.ExpectedError)
 
 class SetRequest (BaseRequest):
@@ -191,14 +218,19 @@ class SetRequest (BaseRequest):
   inspection.  The `check' method ensures that the password is not empty, but
   imposes no other policy restrictions.
   """
+
+  OP = 'set'
+
   def __init__(me, accts, new):
     me.new = new
     me.ops = [me.makeop(SetOperation, acct.svc, acct.user, passwd = new)
               for acct in accts]
+
   def check(me):
     if me.new == '':
       raise U.ExpectedError, (400, "Empty password not permitted")
     super(SetRequest, me).check()
+
 CONF.export('SetRequest')
 
 class ResetRequest (BaseRequest):
@@ -214,6 +246,8 @@ class ResetRequest (BaseRequest):
   Alternatively, subclasses can override the `pwgen' method.
   """
 
+  OP = 'reset'
+
   ## Password generation parameters.
   PWBYTES = 16
   ENCODING = 'base32'
@@ -229,28 +263,30 @@ class ResetRequest (BaseRequest):
   def pwgen(me):
     return U.ENCODINGS[me.ENCODING].encode(OS.urandom(me.PWBYTES)) \
            .rstrip('=')
+
 CONF.export('ResetRequest')
 
 class ClearRequest (BaseRequest):
   """
   Request to clear the password for the given ACCTS.
   """
+
+  OP = 'clear'
+
   def __init__(me, accts):
     me.ops = [me.makeop(ClearOperation, acct.svc, acct.user)
               for acct in accts]
+
 CONF.export('ClearRequest')
 
 ###--------------------------------------------------------------------------
 ### Master policy switch.
 
-class polswitch (U.struct):
-  __slots__ = ['set', 'reset', 'clear']
-
 CONF.DEFAULTS.update(
 
   ## Map a request type `set', `reset', or `clear', to the appropriate
   ## request class.
-  RQCLASS = polswitch(None, None, None),
+  RQCLASS = polswitch(**dict((i, None) for i in OPS)),
 
   ## Alternatively, set this to a mixin class to apply common policy to all
   ## the kinds of requests.