#/usr/local/bin/python from fedid import fedid from remote_service import service_caller from abac_remote_service import abac_service_caller from service_error import service_error import sys class authorizer_base: """ Classes based on this one keep track of authorization attributes for the various modules running. This base class holds some utility functions that they all potentially use. """ # general error exception for badly formed names. class bad_name(RuntimeError): pass @staticmethod def auth_name(name): """ Helper to convert a non-unicode local name to a unicode string. Mixed representations can needlessly confuse the authorizer. """ if isinstance(name, basestring): if not isinstance(name, unicode): return unicode(name) else: return name else: return name @staticmethod def valid_name(name): """ Ensure that the given name is valid. A valid name can either be a triple of strings and fedids representing one of our generalized Emulab names or a single fedid. Compound names can include wildcards (None) and must anchor to a fedid at their highest level (unless they're all None) This either returns True or throws an exception. More an assertion than a function. """ if isinstance(name, tuple) and len(name) == 3: for n in name: if n: if not (isinstance(n, basestring) or isinstance(n, fedid)): raise authorizer_base.bad_name( "names must be either a triple or a fedid") for n in name: if n: if isinstance(n, fedid): return True else: raise authorizer_base.bad_name( "Compound names must be " + \ "rooted in fedids: %s" % str(name)) return True elif isinstance(name, fedid): return True else: raise authorizer_base.bad_name( "Names must be a triple or a fedid (%s)" % name) class authorizer(authorizer_base): """ This class keeps track of authorization attributes for the various modules running. When it gets smarter it will be the basis for a real attribute-based authentication system. """ def __init__(self, def_attr="testbed"): self.attrs = { } self.globals=set() def set_attribute(self, name, attr): """ Attach attr to name. Multiple attrs can be attached. """ self.valid_name(name) if isinstance(name, tuple): aname = tuple([ self.auth_name(n) for n in name]) else: aname = self.auth_name(name) if not self.attrs.has_key(aname): self.attrs[aname] = set() self.attrs[aname].add(attr) def unset_attribute(self, name, attr): """ Remove an attribute from name """ self.valid_name(name) if isinstance(name, tuple): aname = tuple([ self.auth_name(n) for n in name]) else: aname = self.auth_name(name) attrs = self.attrs.get(aname, None) if attrs: attrs.discard(attr) def check_attribute(self, name, attr): """ Return True if name has attr (or if attr is global). Tuple names match any tuple name that matches all names present and has None entries in other fileds. For tuple names True implies that there is a matching tuple name with the attribute. """ def tup(tup, i, p): mask = 1 << i if p & mask : return authorizer.auth_name(tup[i]) else: return None self.valid_name(name) if attr in self.globals: return True if isinstance(name, tuple): for p in range(0,8): lookup = ( tup(name, 0, p), tup(name,1, p), tup(name,2,p)) if self.attrs.has_key(lookup): if attr in self.attrs[lookup]: return True else: return attr in self.attrs.get(self.auth_name(name), set()) def set_global_attribute(self, attr): """ Set a global attribute. All names, even those otherwise unknown to the authorizer have this attribute. """ self.globals.add(attr) def unset_global_attribute(self, attr): """ Remove a global attribute """ self.globals.discard(attr) def __str__(self): rv = "" rv += "attrs %s\n" % self.attrs rv += "globals %s" % self.globals return rv class abac_authorizer(authorizer_base): """ Use the ABAC authorization system to make attribute decisions. """ def __init__(self, url=None, cert_file=None, cert_pwd=None, trusted_certs=None, me=None): self.call_CreateContext = abac_service_caller('CreateContext') self.call_CredentialUpdate = abac_service_caller('CredentialUpdate') self.call_Access = abac_service_caller('Access') self.globals = set() self.url = url self.cert_file = cert_file self.cert_pwd = cert_pwd self.trusted_certs = trusted_certs self.me = me req = { 'context': { 'self': me.get_hexstr()} } # May throw a service error resp = self.call_CreateContext(self.url, req, self.cert_file, self.cert_pwd, self.trusted_certs, tracefile=sys.stderr) if resp.has_key('CreateContextResponseBody'): resp = resp['CreateContextResponseBody'] if resp.has_key('contextID'): self.contextID = resp['contextID'] else: raise service_error(service_error.internal, "No context ID for the new authorization context") else: raise service_error(service_error.internal, "Bad response to creating service context") def set_attribute(self, name, attr): self.valid_name(name) if isinstance(name, tuple): if not isinstance(name[0], fedid): raise service_error(service_error.internal, "Triple not anchored in testbed") if not name[1] and not name[2]: raise service_error(service_error.internal, "Anonymous access not yet permitted") preconditions = "^".join( [ "%s.%s" % (name[0], self.auth_name(n)) \ for n in name[1:3] if n ]) req = { 'credential': [ '%s.%s<--%s' % \ ( self.me, attr, preconditions) ], 'context': { 'contextID': self.contextID } } else: req = { 'credential': [ '%s.%s<--%s' % \ ( self.me, attr, self.auth_name(name)) ], 'context': { 'contextID': self.contextID } } print req self.call_CredentialUpdate(self.url, req, self.cert_file, self.cert_pwd, self.trusted_certs, tracefile=sys.stderr) def check_attribute(self, name, attr): self.valid_name(name) if attr in self.globals: return True if isinstance(name, tuple): raise service_error(service_error.internal, "Only fedids permitted here") req = { 'goal': '%s:%s<<---%s' % (self.me, attr, name), 'context': { 'contextID': self.contextID } } resp = self.call_Access(self.url, req, self.cert_file, self.cert_pwd, self.trusted_certs) if resp.has_key('AccessResponseBody'): resp = resp['AccessResponseBody'] result = resp.get('result', None) return (result and result.lower() == 'true') def set_global_attribute(self, attr): """ Set a global attribute. All names, even those otherwise unknown to the authorizer have this attribute. """ self.globals.add(attr) def unset_global_attribute(self, attr): """ Remove a global attribute """ self.globals.discard(attr)