Index: fedd/fedd_experiment_control.py
===================================================================
--- fedd/fedd_experiment_control.py	(revision 0df60150b110eae6789833a3ad85401a986d8a74)
+++ fedd/fedd_experiment_control.py	(revision 406474250e0aebb80188688de6fc8a03570fbfb2)
@@ -178,4 +178,5 @@
 			self.trusted_certs = config.get(s, "trusted_certs")
 
+
 	self.exp_stem = "fed-stem"
 	self.log = logging.getLogger("fedd.experiment_control")
@@ -190,4 +191,5 @@
 	self.ssh_identity_file = None
 
+	# XXX remove if/else?
 	if config.has_section("experiment_control"):
 	    self.debug = config.get("experiment_control", "create_debug")
@@ -196,4 +198,5 @@
 	    self.splitter_url = config.get("experiment_control", "splitter_url")
 	    self.fedkit = config.get("experiment_control", "fedkit")
+	    accessdb_file = config.get("experiment_control", "accessdb")
 	else:
 	    self.debug = False
@@ -236,5 +239,4 @@
 	if self.ssh_pubkey_file:
 	    try:
-		print "reading %s" % self.ssh_pubkey_file
 		f = open(self.ssh_pubkey_file, 'r')
 		self.ssh_pubkey = f.read()
@@ -252,4 +254,24 @@
 
 	set_log_level(config, "experiment_control", self.log)
+
+	if auth:
+	    self.auth = auth
+	else:
+	    self.log.error(\
+		    "[access]: No authorizer initialized, creating local one.")
+	    auth = authorizer()
+
+	if accessdb_file:
+	    try:
+		self.accessdb = self.read_accessdb(accessdb_file)
+	    except IOError:
+		raise service_error(service_error.internal,
+			"Error opening %s as experiment control accessdb" % \
+				accessdb_file)
+	    for fid in self.accessdb.keys():
+		self.auth.set_attribute(fid, 'create')
+	else:
+	    raise service_error(service_error.internal,
+		    "No accessdb specified in config")
 
 	# Grab saved state.  OK to do this w/o locking because it's read only
@@ -360,4 +382,58 @@
 	    self.log.warning(("[read_state]: No saved state: " + \
 		    "Unpickling failed: %s") % e)
+	
+	for k in self.state.keys():
+	    try:
+		# This list should only have one element in it, but phrasing it
+		# as a for loop doesn't cost much, really.  We have to find the
+		# fedid elements anyway.
+		for eid in [ f['fedid'] \
+			for f in self.state[k]['experimentID']\
+			    if f.has_key('fedid') ]:
+		    self.auth.set_attribute(self.state[k]['owner'], eid)
+	    except KeyError, e:
+		self.log.warning("[read_state]: State ownership or identity " +\
+			"misformatted in %s: %s" % (self.state_filename, e))
+
+
+    def read_accessdb(self, accessdb_file):
+	access = {}
+	name_expr = "[" + string.ascii_letters + string.digits + "\.\-]+"
+	project_line = re.compile("^\s*fedid:([" + string.hexdigits + "]+)"+ \
+		"\s*->\(\s*("+name_expr+")\s*,\s*("+name_expr+")\s*\)\s*$")
+	user_line = re.compile("^\s*fedid:([" + string.hexdigits + "]+)"+ \
+		"\s*->\s*(" + name_expr + ")\s*$")
+	lineno = 0
+
+	f = open(accessdb_file, "r")
+	for line in f:
+	    lineno += 1
+	    line.strip()
+	    if len(line) == 0 or line.startswith('#'):
+		continue
+	    m = project_line.match(line)
+	    if m:
+		fid = fedid(hexstr=m.group(1))
+		project, user = m.group(2,3)
+		if not access.has_key(fid):
+		    access[fid] = []
+		access[fid].append((project, user))
+		continue
+
+	    m = user_line.match(line)
+	    if m:
+		fid = fedid(hexstr=m.group(1))
+		project = None
+		user = m.group(2)
+		if not access.has_key(fid):
+		    access[fid] = []
+		access[fid].append((project, user))
+		continue
+	    raise service_error(service_error.internal,
+		    "Error parsing access db %s at line %d" % \
+			(accessdb_file, lineno))
+	f.close()
+	return access
+
 
     def scp_file(self, file, user, host, dest=""):
@@ -793,5 +869,6 @@
 	else: return None
 
-    def get_access(self, tb, nodes, user, tbparam, master, export_project):
+    def get_access(self, tb, nodes, user, tbparam, master, export_project,
+	    access_user):
 	"""
 	Get access to testbed through fedd and set the parameters for that tb
@@ -824,43 +901,66 @@
 
 
-	# The basic request
-	req = {\
-		'destinationTestbed' : { 'uri' : uri },
-		'user':  user,
-		'allocID' : { 'localname': 'test' },
-		'createAccess' : [ { 'sshPubkey' : self.ssh_pubkey } ],
-		'serviceAccess' : service_keys
-	    }
-
-	print "service %s" % service_keys
-	print "ssh pubkey %s" % self.ssh_pubkey
-	print "ssh pubkey file %s" % self.ssh_pubkey_file
-
-	if tb == master:
-	    # NB, the export_project parameter is a dict that includes
-	    # the type
-	    req['exportProject'] = export_project
-
-	# node resources if any
-	if nodes != None and len(nodes) > 0:
-	    rnodes = [ ]
-	    for n in nodes:
-		rn = { }
-		image, hw, count = n.split(":")
-		if image: rn['image'] = [ image ]
-		if hw: rn['hardware'] = [ hw ]
-		if count: rn['count'] = int(count)
-		rnodes.append(rn)
-	    req['resources']= { }
-	    req['resources']['node'] = rnodes
-
-	r = self.call_RequestAccess(uri, req, 
-		self.cert_file, self.cert_pwd, self.trusted_certs)
-
-	if r.has_key('RequestAccessResponseBody'):
-	    r = r['RequestAccessResponseBody']
-	else:
-	    raise service_error(service_error.protocol,
-		    "Bad proxy response")
+	for p, u in access_user:
+
+	    if p:
+		# Request with user and project specified
+		req = {\
+			'destinationTestbed' : { 'uri' : uri },
+			'project': { 
+			    'name': {'localname': p},
+			    'user': [ {'userID': { 'localname': u } } ],
+			    },
+			'user':  user,
+			'allocID' : { 'localname': 'test' },
+			'createAccess' : [ { 'sshPubkey' : self.ssh_pubkey } ],
+			'serviceAccess' : service_keys
+		    }
+	    else:
+		# Request with only user specified
+		req = {\
+			'destinationTestbed' : { 'uri' : uri },
+			'user':  [ {'userID': { 'localname': u } } ],
+			'allocID' : { 'localname': 'test' },
+			'createAccess' : [ { 'sshPubkey' : self.ssh_pubkey } ],
+			'serviceAccess' : service_keys
+		    }
+
+	    if tb == master:
+		# NB, the export_project parameter is a dict that includes
+		# the type
+		req['exportProject'] = export_project
+
+	    # node resources if any
+	    if nodes != None and len(nodes) > 0:
+		rnodes = [ ]
+		for n in nodes:
+		    rn = { }
+		    image, hw, count = n.split(":")
+		    if image: rn['image'] = [ image ]
+		    if hw: rn['hardware'] = [ hw ]
+		    if count: rn['count'] = int(count)
+		    rnodes.append(rn)
+		req['resources']= { }
+		req['resources']['node'] = rnodes
+
+	    try:
+		r = self.call_RequestAccess(uri, req, 
+			self.cert_file, self.cert_pwd, self.trusted_certs)
+	    except service_error, e:
+		if e.code == service_error.access:
+		    r = None
+		    continue
+		else:
+		    raise e
+
+	    if r.has_key('RequestAccessResponseBody'):
+		r = r['RequestAccessResponseBody']
+	    else:
+		raise service_error(service_error.protocol,
+			"Bad proxy response")
+	
+	if not r:
+	    raise service_error(service_error.access, 
+		    "Access denied by %s (%s)" % (tb, uri))
 
 	e = r['emulab']
@@ -1031,5 +1131,6 @@
 	    self.get_access = get_access
 
-	def __call__(self, line, user, tbparams, master, export_project):
+	def __call__(self, line, user, tbparams, master, export_project,
+		access_user):
 	    # Testbed access parameters
 	    if not self.in_allbeds:
@@ -1046,5 +1147,5 @@
 		    tb = nodes.pop(0)
 		    self.get_access(tb, nodes, user, tbparams, master,
-			    export_project)
+			    export_project, access_user)
 		return True
 
@@ -1355,4 +1456,8 @@
 	to instantiate them and start it all up.
 	"""
+
+	if not self.auth.check_attribute(fid, 'create'):
+	    raise service_error(service_error.access, "Create access denied")
+
 	try:
 	    tmpdir = tempfile.mkdtemp(prefix="split-")
@@ -1366,4 +1471,10 @@
 	tclfile = tmpdir + "/experiment.tcl"
 	tbparams = { }
+	try:
+	    access_user = self.accessdb[fid]
+	except KeyError:
+	    raise service_error(service_error.internal,
+		    "Access map and authorizer out of sync in " + \
+			    "create_experiment for fedid %s"  % fid)
 
 	pid = "dummy"
@@ -1479,5 +1590,6 @@
 	    if parse_current_testbed(line, master, allocated, tbparams):
 		continue
-	    elif parse_allbeds(line, user, tbparams, master, export_project):
+	    elif parse_allbeds(line, user, tbparams, master, export_project,
+		    access_user):
 		continue
 	    elif parse_gateways(line, allocated, tbparams):
@@ -1620,4 +1732,5 @@
 		    'vtopo': vtopo,\
 		    'vis' : vis,
+		    'owner': fid,
 		    'experimentID' : [\
 			    { 'fedid': expid }, { 'localname': eid },\
@@ -1628,4 +1741,7 @@
 	self.state_lock.release()
 
+	self.auth.set_attribute(fid, expid)
+	self.auth.set_attribute(expid, expid)
+
 	if not failed:
 	    return resp
@@ -1633,4 +1749,39 @@
 	    raise service_error(service_error.partial, \
 		    "Partial swap in on %s" % ",".join(succeeded))
+
+    def check_experiment_access(self, fid, key):
+	"""
+	Confirm that the fid has access to the experiment.  Though a request
+	may be made in terms of a local name, the access attribute is always
+	the experiment's fedid.
+	"""
+	if not isinstance(key, fedid):
+	    self.state_lock.acquire()
+	    if self.state.has_key(key):
+		try:
+		    kl = [ f['fedid'] for f in self.state[key]['experimentID']\
+			    if f.has_key('fedid') ]
+		except KeyError:
+		    self.state_lock.release()
+		    raise service_error(service_error.internal, 
+			    "No fedid for experiment %s when checking " +\
+				    "access(!?)" % key)
+		if len(kl) == 1:
+		    key = kl[0]
+		else:
+		    self.state_lock.release()
+		    raise service_error(service_error.internal, 
+			    "multiple fedids for experiment %s when " +\
+				    "checking access(!?)" % key)
+	    else:
+		self.state_lock.release()
+		raise service_error(service_error.access, "Access Denied")
+	    self.state_lock.release()
+
+	if self.auth.check_attribute(fid, key):
+	    return True
+	else:
+	    raise service_error(service_error.access, "Access Denied")
+
 
 
@@ -1658,4 +1809,6 @@
 	    raise service_error(service_error.req, "No request?")
 
+	self.check_experiment_access(fid, key)
+
 	self.state_lock.acquire()
 	if self.state.has_key(key):
@@ -1691,4 +1844,6 @@
 	    raise service_error(service_error.req, "No request?")
 
+	self.check_experiment_access(fid, key)
+
 	self.state_lock.acquire()
 	if self.state.has_key(key):
@@ -1723,4 +1878,6 @@
 	else:
 	    raise service_error(service_error.req, "No request?")
+
+	self.check_experiment_access(fid, key)
 
 	# The state may be massaged by the service function that called
@@ -1758,4 +1915,6 @@
 	else:
 	    raise service_error(service_error.req, "No request?")
+
+	self.check_experiment_access(fid, key)
 
 	self.state_lock.acquire()
