### <http://www.gnu.org/licenses/>.
import os as OS
+import syslog as L
import config as CONF; CFG = CONF.CFG
import util as U
"""A fake operation which just raises an exception."""
def __init__(me, svc, user, exc):
me.svc = svc
- me.uesr = user
+ me.user = user
me.exc = exc
def perform(me):
me.result = None
###--------------------------------------------------------------------------
### 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
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 describe(me):
+ return me.OP
+
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):
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):
Alternatively, subclasses can override the `pwgen' method.
"""
+ OP = 'reset'
+
## Password generation parameters.
PWBYTES = 16
ENCODING = 'base32'
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')
###--------------------------------------------------------------------------
* a list of the individual operation objects.
"""
rq = getattr(CFG.RQCLASS, op)(accts, *args, **kw)
- rq.check()
+ desc = rq.describe()
+ if not CFG.OPTS.ignpol:
+ try:
+ rq.check()
+ except U.ExpectedError, e:
+ L.syslog('REFUSE %s %s: %s' %
+ (desc,
+ ', '.join(['%s@%s' % (o.user, o.svc.name) for o in rq.ops]),
+ e))
+ raise
ops = rq.perform()
nwin = nlose = 0
for o in ops:
else:
if nlose: rc = outcome.FAIL
else: rc = outcome.NOTHING
+ L.syslog('%s %s: %s' % (['OK', 'PARTIAL', 'FAIL', 'NOTHING'][rc],
+ desc,
+ '; '.join(['%s@%s %s' % (o.user, o.svc.name,
+ not o.error and 'OK' or
+ 'ERR %s' % o.error)
+ for o in ops])))
ii = [info(v, getattr(rq, k)) for k, v in rq.INFO.iteritems()]
return outcome(rc, nwin, nlose), ii, rq, ops