Index: fedd/authorizer.py
===================================================================
--- fedd/authorizer.py	(revision 3f6bc5f7efe311dabca7038aff4d0d6035991208)
+++ fedd/authorizer.py	(revision 3f6bc5f7efe311dabca7038aff4d0d6035991208)
@@ -0,0 +1,109 @@
+#/usr/local/bin/python
+
+from fedid import fedid
+
+class authorizer:
+    """
+    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.
+    """
+    # general error exception for badly formed names.  
+    class bad_name(RuntimeError): pass
+
+    def __init__(self, def_attr="testbed"):
+	self.attrs = { }
+
+    @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
+
+    def valid_name(self, 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 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 self.bad_name("Compound names must be " + \
+				"rooted in fedids: %s" % str(name))
+
+	    return True
+	elif isinstance(name, fedid):
+	    return True
+	else:
+	    raise bad_name("Names must be a triple or a fedid (%s)" % name)
+
+    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.  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 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())
+
+
Index: fedd/fedd_access.py
===================================================================
--- fedd/fedd_access.py	(revision c971895548110b65e278830371b081586409a560)
+++ fedd/fedd_access.py	(revision 3f6bc5f7efe311dabca7038aff4d0d6035991208)
@@ -15,4 +15,5 @@
 from fedd_access_project import access_project
 from fedid import fedid, generate_fedid
+from authorizer import authorizer
 import parse_detail
 from service_error import *
@@ -50,5 +51,5 @@
 		    'RequestAccessRequestBody')
 
-    def __init__(self, config=None):
+    def __init__(self, config=None, auth=None):
 	"""
 	Initializer.  Pulls parameters out of the ConfigParser's access section.
@@ -63,5 +64,4 @@
 	else:
 	    raise RunTimeError("No config to fedd_access")
-
 
 	# Create instance attributes from the static lists
@@ -90,17 +90,20 @@
 	    'keys' : self.keys
 	}
+	self.log = logging.getLogger("fedd.access")
+	set_log_level(config, "access", self.log)
 	self.state_lock = Lock()
+
+	if auth: self.auth = auth
+	else:
+	    self.log.error(\
+		    "[access]: No authorizer initialized, creating local one.")
+	    auth = authorizer()
+
 	self.fedid_default = "testbed"
 	if config.has_option("access", "accessdb"):
 	    self.read_access(config.get("access", "accessdb"))
-	if config.has_option("access", "trustdb"):
-	    self.read_trust(config.get("access", "trustdb"))
 
 	self.state_filename = config.get("access", "access_state", "")
-	self.log = logging.getLogger("fedd.access")
-	set_log_level(config, "access", self.log)
 	self.read_state()
-
-
 	# Certs are promoted from the generic to the specific, so without a
 	# specific proxy certificate, the main certificates are used for
@@ -166,8 +169,8 @@
 	if not config.has_option("access", "dynamic_projects_url"):
 	    self.allocate_project = \
-		fedd_allocate_project_local(config)
+		fedd_allocate_project_local(config, auth)
 	else:
 	    self.allocate_project = \
-		fedd_allocate_project_remote(config)
+		fedd_allocate_project_remote(config, auth)
 
 	# If the project allocator exports services, put them in this object's
@@ -176,53 +179,4 @@
 	self.xmlrpc_services.update(self.allocate_project.xmlrpc_services)
 
-    def read_trust(self, trust):
-	"""
-	Read a trust file that splits fedids into testbeds, users or projects
-
-	Format is:
-
-	[type]
-	fedid
-	fedid
-	default: type
-	"""
-	lineno = 0;
-	cat = None
-	cat_re = re.compile("\[(user|testbed|project)\]$", re.IGNORECASE)
-	fedid_re = re.compile("[" + string.hexdigits + "]+$")
-	default_re = re.compile("default:\s*(user|testbed|project)$", 
-		re.IGNORECASE)
-
-	f = open(trust, "r")
-	for line in f:
-	    lineno += 1
-	    line = line.strip()
-	    if len(line) == 0 or line.startswith("#"):
-		continue
-	    # Category line
-	    m = cat_re.match(line)
-	    if m != None:
-		cat = m.group(1).lower()
-		continue
-	    # Fedid line
-	    m = fedid_re.match(line)
-	    if m != None:
-		if cat != None:
-		    self.fedid_category[fedid(hexstr=m.string)] = cat
-		else:
-		    raise self.parse_error(\
-			    "Bad fedid in trust file (%s) line: %d" % \
-			    (trust, lineno))
-		continue
-	    # default line
-	    m = default_re.match(line)
-	    if m != None:
-		self.fedid_default = m.group(1).lower()
-		continue
-	    # Nothing matched - bad line, raise exception
-	    f.close()
-	    raise self.parse_error(\
-		    "Unparsable line in trustfile %s line %d" % (trust, lineno))
-	f.close()
 
     def read_access(self, config):
@@ -270,6 +224,13 @@
 
 	def parse_name(n):
-	    if n.startswith('fedid:'): return fedid(n[len('fedid:'):])
+	    if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):])
 	    else: return n
+	
+	def auth_name(n):
+	    if isinstance(n, basestring):
+		if n =='<any>' or n =='<none>': return None
+		else: return unicode(n)
+	    else:
+		return n
 
 	f = open(config, "r");
@@ -298,4 +259,5 @@
 	    if m != None:
 		access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
+		auth_key = tuple([ auth_name(x) for x in access_key])
 		aps = m.group(4).split(":");
 		if aps[0] == 'fedid:':
@@ -310,4 +272,5 @@
 
 		self.access[access_key] = access_val
+		self.auth.set_attribute(auth_key, "access")
 		continue
 
@@ -386,4 +349,9 @@
 		    "Unpickling failed: %s") % e)
 
+	for k in self.allocation.keys():
+	    for o in self.allocation[k].get('owners', []):
+		self.auth.set_attribute(o, fedid(hexstr=k))
+
+
     def permute_wildcards(self, a, p):
 	"""Return a copy of a with various fields wildcarded.
@@ -440,9 +408,4 @@
 	ru = None
 
-
-	principal_type = self.fedid_category.get(fid, self.fedid_default)
-
-	if principal_type == "testbed": tb = fid
-
 	if req.has_key('project'):
 	    p = req['project']
@@ -453,28 +416,44 @@
 	    user = self.get_users(req)
 
-	# Now filter by prinicpal type
-	if principal_type == "user":
-	    if user != None:
-		fedids = [ u for u in user if isinstance(u, type(fid))]
+	user_fedids = [ u for u in user if isinstance(u, fedid)]
+	# Determine how the caller is representing itself.  If its fedid shows
+	# up as a project or a singleton user, let that stand.  If neither the
+	# usernames nor the project name is a fedid, the caller is a testbed.
+	if project and isinstance(project, fedid):
+	    if project == fid:
+		# The caller is the project (which is already in the tuple
+		# passed in to the authorizer)
+		owners = user_fedids
+		owners.append(project)
+	    else:
+		raise service_error(service_error.req,
+			"Project asserting different fedid")
+	else:
+	    if fid not in user_fedids:
+		tb = fid
+		owners = user_fedids
+		owners.append(fid)
+	    else:
 		if len(fedids) > 1:
 		    raise service_error(service_error.req,
-			    "User asserting multiple fedids")
-		elif len(fedids) == 1 and fedids[0] != fid:
-		    raise service_error(service_error.req,
 			    "User asserting different fedid")
-	    project = None
-	    tb = None
-	elif principal_type == "project":
-	    if isinstance(project, type(fid)) and fid != project:
-		raise service_error(service_error.req,
-			"Project asserting different fedid")
-	    tb = None
-
-	# Ready to look up access
+		else:
+		    # Which is a singleton
+		    owners = user_fedids
+	# Confirm authorization
+	for u in user:
+	    if self.auth.check_attribute((tb, project, u), 'access'):
+		print "Access OK"
+		break
+	else:
+	    print "Access failed"
+	    raise service_error(service_error.access, "Access denied")
+
+	# This maps a valid user to the Emulab projects and users to use
 	found, user_match = self.find_access((tb, project, user))
 	
 	if found == None:
 	    raise service_error(service_error.access,
-		    "Access denied")
+		    "Access denied - cannot map access")
 
 	# resolve <dynamic> and <same> in found
@@ -521,5 +500,6 @@
 	    dyn_service_user = True
 
-	return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj)
+	return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\
+		owners
 
     def build_response(self, alloc_id, ap):
@@ -571,5 +551,5 @@
 	if dt == None or dt == self.testbed:
 	    # Request for this fedd
-	    found, dyn = self.lookup_access(req, fid)
+	    found, dyn, owners = self.lookup_access(req, fid)
 	    restricted = None
 	    ap = None
@@ -687,6 +667,9 @@
 			"Misformed allocation response?")
 
+	    self.allocation[aid]['owners'] = owners
 	    self.write_state()
 	    self.state_lock.release()
+	    for o in owners:
+		self.auth.set_attribute(o, allocID)
 	    resp = self.build_response({ 'fedid': allocID } , ap)
 	    return resp
@@ -709,7 +692,8 @@
 	try:
 	    if req['allocID'].has_key('localname'):
-		aid = req['allocID']['localname']
+		auth_attr = aid = req['allocID']['localname']
 	    elif req['allocID'].has_key('fedid'):
 		aid = unicode(req['allocID']['fedid'])
+		auth_attr = req['allocID']['fedid']
 	    else:
 		raise service_error(service_error.req,
@@ -717,4 +701,8 @@
 	except KeyError:
 	    raise service_error(service_error.req, "Badly formed request")
+
+	print "Checking for %s %s" % (fid, auth_attr)
+	if not self.auth.check_attribute(fid, auth_attr):
+	    raise service_error(service_error.access, "Access Denied")
 
 	# If we know this allocation, reduce the reference counts and remove
@@ -724,4 +712,6 @@
 	# duplicates.  del_project is just the name of any dynamic project to
 	# delete.
+	# We're somewhat lazy about deleting authorization attributes.  Having
+	# access to something that doesn't exist isn't harmful.
 	del_users = { }
 	del_project = None
Index: fedd/fedd_allocate_project.py
===================================================================
--- fedd/fedd_allocate_project.py	(revision c971895548110b65e278830371b081586409a560)
+++ fedd/fedd_allocate_project.py	(revision 3f6bc5f7efe311dabca7038aff4d0d6035991208)
@@ -33,5 +33,5 @@
     Allocate projects on this machine in response to an access request.
     """
-    def __init__(self, config):
+    def __init__(self, config, auth=None):
 	"""
 	Initializer.  Parses a configuration if one is given.
@@ -387,5 +387,5 @@
 
     # back to defining the fedd_allocate_project_remote class
-    def __init__(self, config):
+    def __init__(self, config, auth=None):
 	"""
 	Initializer.  Parses a configuration if one is given.
Index: fedd/fedd_deter_impl.py
===================================================================
--- fedd/fedd_deter_impl.py	(revision c971895548110b65e278830371b081586409a560)
+++ fedd/fedd_deter_impl.py	(revision 3f6bc5f7efe311dabca7038aff4d0d6035991208)
@@ -4,4 +4,6 @@
 from fedd_experiment_control import fedd_experiment_control_local
 from fedd_split import fedd_split_local
+
+from authorizer import authorizer
 
 class fedd_deter_impl:
@@ -22,4 +24,5 @@
 	self.soap_services = { }
 	self.xmlrpc_services = { }
+	self.auth = authorizer()
 
 	if config:
@@ -29,15 +32,16 @@
 
 	    if config.has_section("access"):
-		self.access = fedd_access(config)
+		self.access = fedd_access(config, self.auth)
 		self.soap_services.update(self.access.soap_services) 
 		self.xmlrpc_services.update(self.access.xmlrpc_services) 
 
 	    if config.has_section("experiment_control"):
-		self.experiment = fedd_experiment_control_local(config)
+		self.experiment = \
+			fedd_experiment_control_local(config, self.auth)
 		self.soap_services.update(self.experiment.soap_services) 
 		self.xmlrpc_services.update(self.experiment.xmlrpc_services) 
 
 	    if config.has_section("splitter"):
-		self.splitter = fedd_split_local(config)
+		self.splitter = fedd_split_local(config, self.auth)
 		self.soap_services.update(self.splitter.soap_services) 
 		self.xmlrpc_services.update(self.splitter.xmlrpc_services) 
Index: fedd/fedd_experiment_control.py
===================================================================
--- fedd/fedd_experiment_control.py	(revision c971895548110b65e278830371b081586409a560)
+++ fedd/fedd_experiment_control.py	(revision 3f6bc5f7efe311dabca7038aff4d0d6035991208)
@@ -154,5 +154,5 @@
 	    Ns2SplitRequestMessage, 'Ns2SplitRequestBody')
 
-    def __init__(self, config=None):
+    def __init__(self, config=None, auth=None):
 	"""
 	Intialize the various attributes, most from the config object
Index: fedd/fedd_split.py
===================================================================
--- fedd/fedd_split.py	(revision c971895548110b65e278830371b081586409a560)
+++ fedd/fedd_split.py	(revision 3f6bc5f7efe311dabca7038aff4d0d6035991208)
@@ -22,5 +22,5 @@
 
 class fedd_split_local:
-    def __init__(self, config=None):
+    def __init__(self, config=None, auth=None):
 	"""
 	Intialize the various attributes, most from the config object
