source: fedd/fedd_proj.py @ 21a1c30

axis_examplecompt_changesinfo-opsversion-1.30version-2.00version-3.01version-3.02
Last change on this file since 21a1c30 was 21a1c30, checked in by Ted Faber <faber@…>, 16 years ago

clear out some unused code, small bug

  • Property mode set to 100644
File size: 23.9 KB
Line 
1#!/usr/local/bin/python
2
3import os,sys
4
5from BaseHTTPServer import BaseHTTPRequestHandler
6from ZSI import *
7from M2Crypto import SSL
8from M2Crypto.m2xmlrpclib import SSL_Transport
9from M2Crypto.SSL.SSLServer import SSLServer
10import M2Crypto.httpslib
11import xmlrpclib
12
13import re
14import random
15import string
16import subprocess
17import tempfile
18import copy
19
20from fedd_services import *
21from fedd_util import *
22import parse_detail
23
24class fedd_proj:
25    """
26    The implementation of access control based on mapping users to projects.
27
28    Users can be mapped to existing projects or have projects created
29    dynamically.  This implements both direct requests and proxies.
30    """
31    # Attributes that can be parsed from the configuration file
32    bool_attrs = ("dynamic_projects", "project_priority")
33    emulab_attrs = ("boss", "ops", "domain", "fileserver", "eventserver")
34    id_attrs = ("testbed", "cert_file", "trusted_certs", "proxy",
35            "proxy_trusted_certs", "cert_pwd")
36
37    # Used by the SOAP caller
38    soap_namespace = 'http://www.isi.edu/faber/fedd.wsdl'
39    soap_methods = { 'RequestAccess': 'soap_RequestAccess' }
40    xmlrpc_methods = { 'RequestAccess': 'xmlrpc_RequestAccess' }
41
42    class access_project:
43        """
44        A project description used to grant access to this testbed.
45
46        The description includes a name and a list of node types to which the
47        project will be granted access.
48        """
49        def __init__(self, name, nt):
50            self.name = name
51            self.node_types = list(nt)
52
53        def __repr__(self):
54            if len(self.node_types) > 0:
55                return "access_proj('%s', ['%s'])" % \
56                        (self.name, str("','").join(self.node_types))
57            else:
58                return "access_proj('%s', [])" % self.name
59
60    # This is used to make the service error reporting independent of the
61    # transport.  The XMLRPC and SOAP dispatchers will convert it into
62    # transport-specific errors
63    class service_error(RuntimeError):
64        access = 1
65        proxy= 2
66        req = 3
67        server_config = 4
68        internal = 5
69        code_str = { 
70            access : "Access Denied",
71            proxy : "Proxy Error",
72            req : "Badly Formed Request",
73            server_config: "Server Configuration Error",
74            internal : "Internal Error"
75        }
76        str_code = dict([ (v, k) for k, v in code_str.iteritems() ])
77        client_errors = ( req )
78        server_errors = ( access, proxy, server_config, internal)
79
80        def __init__(self, code=None, desc=None, from_string=None):
81            self.code = code
82            self.desc = desc
83            if code == None:
84                self.set_code_from_string(from_string)
85            RuntimeError.__init__(self, desc)
86
87        def code_string(self, code=None):
88            code = code or self.code
89            return fedd_proj.service_error.code_str.get(code)
90       
91        def set_code_from_string(self, errstr):
92            self.code = fedd_proj.service_error.str_code.get(errstr,
93                   fedd_proj. service_error.internal)
94            return self.code
95       
96        def is_client_error(self):
97            return self.code in fedd_proj.service_error.client_errors
98
99        def is_server_error(self):
100            return self.code in fedd_proj.service_error.server_errors
101
102    # Used to report errors parsing the configuration files, not in providing
103    # service
104    class parse_error(RuntimeError): pass
105
106
107    def __init__(self, config=None):
108        """
109        Initializer.  Parses a configuration if one is given.
110        """
111
112        # Create instance attributes from the static lists
113        for a in fedd_proj.bool_attrs:
114            setattr(self, a, False)
115
116        for a in fedd_proj.emulab_attrs + fedd_proj.id_attrs:
117            setattr(self, a, None)
118
119        # Other attributes
120        self.attrs = {}
121        self.access = {}
122        self.fedid_category = {}
123        self.fedid_default = "user"
124        self.restricted = []
125        self.wap = '/usr/testbed/sbin/wap'
126        self.newproj = '/usr/testbed/sbin/newproj'
127        self.mkproj = '/usr/testbed/sbin/mkproj'
128        self.grantnodetype = '/usr/testbed/sbin/grantnodetype'
129
130        # Read the configuration
131        if config != None: 
132            self.read_config(config)
133
134    def dump_state(self):
135        """
136        Dump the state read from a configuration file.  Mostly for debugging.
137        """
138        for a in fedd_proj.bool_attrs:
139            print "%s: %s" % (a, getattr(self, a ))
140        for a in fedd_proj.emulab_attrs + fedd_proj.id_attrs:
141            print "%s: %s" % (a, getattr(self, a))
142        for k, v in self.attrs.iteritems():
143            print "%s %s" % (k, v)
144        print "Access DB:"
145        for k, v in self.access.iteritems():
146            print "%s %s" % (k, v)
147        print "Trust DB:"
148        for k, v in self.fedid_category.iteritems():
149            print "%s %s" % (k, v)
150        print "Restricted: %s" % str(',').join(sorted(self.restricted))
151
152    def get_id(self, id):
153        """
154        Utility to get an object from the polymorphic IDType.
155       
156        Only fedids and usernames are currently understood.  If neither is
157        present None is returned.  If both are present (which is a bug) the
158        fedid is returned.
159        """
160        if id == None: 
161            return None
162        elif getattr(id, "get_element_fedid", None) != None:
163            return fedid(id.get_element_fedid())
164        elif getattr(id, "get_element_username", None) != None:
165            return id.get_element_username()
166        else:
167            return None
168
169    def get_users(self, obj):
170        """
171        Return a list of the IDs of the users in dict
172        """
173        if obj.has_key('user'):
174            return [ unpack_id(u['userID']) \
175                    for u in obj['user'] if u.has_key('userID') ]
176        else:
177            return None
178
179    def random_string(self, s, n=3):
180        """Append n random ASCII characters to s and return the string"""
181        rv = s
182        for i in range(0,n):
183            rv += random.choice(string.ascii_letters)
184        return rv
185
186    def write_attr_xml(self, file, root, lines):
187        """
188        Write an emulab config file for a dynamic project.
189
190        Format is <root><attribute name=lines[0]>lines[1]</attribute></root>
191        """
192        # Convert a pair to an attribute line
193        out_attr = lambda a,v : \
194                '<attribute name="%s"><value>%s</value></attribute>' % (a, v)
195
196        f = os.fdopen(file, "w")
197        f.write("<%s>\n" % root)
198        f.write("\n".join([out_attr(*l) for l in lines]))
199        f.write("</%s>\n" % root)
200        f.close()
201
202
203    def dynamic_project(self, found, ssh):
204        """Create a dynamic project with ssh access"""
205        user_fields = [
206                ("name", "Federation User %s" % found[1]),
207                ("email", "%s-fed@isi.deterlab.net" % found[1]),
208                ("password", self.random_string("", 8)),
209                ("login", found[1]),
210                ("address", "4676 Admiralty"),
211                ("city", "Marina del Rey"),
212                ("state", "CA"),
213                ("zip", "90292"),
214                ("country", "USA"),
215                ("phone", "310-448-9190"),
216                ("title", "None"),
217                ("affiliation", "USC/ISI")
218        ]
219
220        proj_fields = [
221                ("name", found[0].name),
222                ("short description", "dynamic federated project"),
223                ("URL", "http://www.isi.edu/~faber"),
224                ("funders", "USC/USU"),
225                ("long description", "Federation access control"),
226                ("public", "1"),
227                ("num_pcs", "100"),
228                ("linkedtous", "1")
229        ]
230
231        # tempfiles for the parameter files
232        uf, userfile = tempfile.mkstemp(prefix="usr", suffix=".xml",
233                dir="/tmp")
234        pf, projfile = tempfile.mkstemp(prefix="proj", suffix=".xml",
235                dir="/tmp")
236
237        # A few more dynamic fields
238        for s in ssh:
239            user_fields.append(("pubkey", s))
240        proj_fields.append(("newuser_xml", userfile))
241
242        # Write out the files
243        self.write_attr_xml(uf, "user", user_fields)
244        self.write_attr_xml(pf, "project", proj_fields)
245
246        # Generate the commands (only grantnodetype's are dynamic)
247        cmds = [
248                (self.wap, self.newproj, projfile),
249                (self.wap, self.mkproj, found[0].name)
250                ]
251        for nt in found[0].node_types:
252            cmds.append((self.wap, self.grantnodetype, '-p', found[0].name, nt))
253
254        # Create the projects
255        rc = 0
256        for cmd in cmds:
257            if self.dynamic_projects:
258                try:
259                    rc = subprocess.call(cmd)
260                except OSerror, e:
261                    raise fedd_proj.service_error(\
262                            fedd_proj.service_error.internal,
263                            "Dynamic project subprocess creation error "+ \
264                                    "[%s] (%s)" %  (cmd[1], e.strerror))
265            else:
266                print >>sys.stdout, str(" ").join(cmd)
267
268            if rc != 0: 
269                raise fedd_proj.service_error(\
270                        fedd_proj.service_error.internal,
271                        "Dynamic project subprocess error " +\
272                                "[%s] (%d)" % (cmd[1], rc))
273        # Clean up tempfiles
274        os.unlink(userfile)
275        os.unlink(projfile)
276
277   
278    def strip_unicode(self, obj):
279        """Loosly de-unicode an object"""
280        if isinstance(obj, dict):
281            for k in obj.keys():
282                obj[k] = self.strip_unicode(obj[k])
283            return obj
284        elif isinstance(obj, basestring):
285            return str(obj)
286        elif getattr(obj, "__iter__", None):
287            return [ self.strip_unicode(x) for x in obj]
288        else:
289            return obj
290
291    def proxy_xmlrpc_request(self, dt, req):
292        """Send an XMLRPC proxy request.  Called if the SOAP RPC fails"""
293        tc = self.proxy_trusted_certs or self.trusted_certs
294
295        # No retry loop here.  Proxy servers must correctly authenticate
296        # themselves without help
297        try:
298            ctx = fedd_ssl_context(self.cert_file, tc, password=self.cert_pwd)
299        except SSL.SSLError:
300            raise fedd_proj.service_error(fedd_proj.service_error.server_config,
301                    "Server certificates misconfigured")
302
303        # Of all the dumbass things.  The XMLRPC library in use here won't
304        # properly encode unicode strings, so we make a copy of req with the
305        # unicode objects converted.  We also convert the destination testbed
306        # to a basic string if it isn't one already.
307        if isinstance(dt, str): url = dt
308        else: url = str(dt)
309
310        r = copy.deepcopy(req)
311        self.strip_unicode(r)
312       
313        transport = SSL_Transport(ctx)
314        port = xmlrpclib.ServerProxy(url, transport=transport)
315
316        # Reconstruct the full request message
317        try:
318            resp = port.RequestAccess(
319                    { "RequestAccessRequestBody": r})
320            resp, method = xmlrpclib.loads(resp)
321        except xmlrpclib.Fault, f:
322            se = fedd_proj.service_error(None, f.faultString, f.faultCode)
323            raise se
324        except xmlrpclib.Error, e:
325            raise fedd_proj.service_error(fedd_proj.service_error.proxy, 
326                    "Remote XMLRPC Fault: %s" % e)
327       
328        if resp[0].has_key('RequestAccessResponseBody'):
329            return resp[0]['RequestAccessResponseBody']
330        else:
331            raise fedd_proj.service_error(fedd_proj.service_error.proxy, 
332                    "Bad proxy response")
333
334    def proxy_request(self, dt, req):
335        """
336        Send req on to the real destination in dt and return the response
337
338        Req is just the requestType object.  This function re-wraps it.  It
339        also rethrows any faults.
340        """
341        tc = self.proxy_trusted_certs or self.trusted_certs
342
343        # No retry loop here.  Proxy servers must correctly authenticate
344        # themselves without help
345        try:
346            ctx = fedd_ssl_context(self.cert_file, tc, password=self.cert_pwd)
347        except SSL.SSLError:
348            raise fedd_proj.service_error(fedd_proj.service_error.server_config, 
349                    "Server certificates misconfigured")
350
351        loc = feddServiceLocator();
352        port = loc.getfeddPortType(dt,
353                transport=M2Crypto.httpslib.HTTPSConnection, 
354                transdict={ 'ssl_context' : ctx })
355
356        # Reconstruct the full request message
357        msg = RequestAccessRequestMessage()
358        msg.set_element_RequestAccessRequestBody(
359                pack_soap(msg, "RequestAccessRequestBody", req))
360        try:
361            resp = port.RequestAccess(msg)
362        except ZSI.ParseException, e:
363            raise fedd_proj.service_error(fedd_proj.service_error.proxy,
364                    "Bad format message (XMLRPC??): %s" %
365                    str(e))
366        r = unpack_soap(resp)
367
368        if r.has_key('RequestAccessResponseBody'):
369            return r['RequestAccessResponseBody']
370        else:
371            raise fedd_proj.service_error(fedd_proj.service_error.proxy,
372                    "Bad proxy response")
373
374    def permute_wildcards(self, a, p):
375        """Return a copy of a with various fields wildcarded.
376
377        The bits of p control the wildcards.  A set bit is a wildcard
378        replacement with the lowest bit being user then project then testbed.
379        """
380        if p & 1: user = ["<any>"]
381        else: user = a[2]
382        if p & 2: proj = "<any>"
383        else: proj = a[1]
384        if p & 4: tb = "<any>"
385        else: tb = a[0]
386
387        return (tb, proj, user)
388
389    def find_access(self, search):
390        """
391        Search the access DB for a match on this tuple.  Return the matching
392        access tuple and the user that matched.
393       
394        NB, if the initial tuple fails to match we start inserting wildcards in
395        an order determined by self.project_priority.  Try the list of users in
396        order (when wildcarded, there's only one user in the list).
397        """
398        if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
399        else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
400
401        for p in perm: 
402            s = self.permute_wildcards(search, p)
403            # s[2] is None on an anonymous, unwildcarded request
404            if s[2] != None:
405                for u in s[2]:
406                    if self.access.has_key((s[0], s[1], u)):
407                        return (self.access[(s[0], s[1], u)], u)
408            else:
409                if self.access.has_key(s):
410                    return (self.access[s], None)
411        return None
412
413    def lookup_access(self, req, fid):
414        """
415        Determine the allowed access for this request.  Return the access and
416        which fields are dynamic.
417
418        The fedid is needed to construct the request
419        """
420        tb = None
421        project = None
422        user = None
423
424        principal_type = self.fedid_category.get(fid, self.fedid_default)
425
426        if principal_type == "testbed": tb = fid
427
428        if req.has_key('project'):
429            p = req['project']
430            if p.has_key('name'):
431                project = unpack_id(p['name'])
432            user = self.get_users(p)
433        else:
434            user = self.get_users(req)
435
436        # Now filter by prinicpal type
437        if principal_type == "user":
438            if user != None:
439                fedids = [ u for u in user if isinstance(u, type(fid))]
440                if len(fedids) > 1:
441                    raise fedd_proj.service_error(service_error.req,
442                            "User asserting multiple fedids")
443                elif len(fedids) == 1 and fedids[0] != fid:
444                    raise fedd_proj.service_error(service_error.req,
445                            "User asserting different fedid")
446            project = None
447            tb = None
448        elif principal_type == "project":
449            if isinstance(project, type(fid)) and fid != project:
450                raise fedd_proj.service_error(service_error.req,
451                        "Project asserting different fedid")
452            tb = None
453
454        # Ready to look up access
455        print "Lookup %s %s %s: " % (tb, project, user)
456        found, user_match = self.find_access((tb, project, user))
457        print "Found: ", found
458       
459        if found == None:
460            raise fedd_proj.service_error(fedd_proj.service_error.access,
461                    "Access denied")
462
463        # resolve <dynamic> and <same> in found
464        dyn_proj = False
465        dyn_user = False
466
467        if found[0].name == "<same>":
468            if project != None:
469                found[0].name = project
470            else : 
471                raise fedd_proj.service_error(\
472                        fedd_proj.service_error.server_config,
473                        "Project matched <same> when no project given")
474        elif found[0].name == "<dynamic>":
475            found[0].name = self.random_string("project", 3)
476            dyn_proj = True
477
478        if found[1] == "<same>":
479            if user_match == "<any>":
480                if user != None: found = (found[0], user[0])
481                else: raise fedd_proj.service_error(\
482                        fedd_proj.service_error.server_config,
483                        "Matched <same> on anonymous request")
484            else:
485                found = (found[0], user_match)
486        elif found[1] == "<dynamic>":
487            found = (found[0], self.random_string("user", 4))
488            dyn_user = True
489       
490        return found, (dyn_user, dyn_proj)
491
492    def build_response(self, alloc_id, ap, ssh):
493        """
494        Create the SOAP response.
495
496        Build the dictionary description of the response and use
497        fedd_utils.pack_soap to create the soap message.  NB that alloc_id is
498        a fedd_services_types.IDType_Holder pulled from the incoming message
499        """
500        # Because alloc_id is already a fedd_services_types.IDType_Holder,
501        # there's no need to repack it
502        msg = { 
503                'allocID': alloc_id,
504                'emulab': { 
505                    'domain': self.domain,
506                    'boss': self.boss,
507                    'ops': self.ops,
508                    'fileServer': self.fileserver,
509                    'eventServer': self.eventserver,
510                    'project': {
511                        'name': pack_id(ap[0].name),
512                        'user': [ {
513                            'userID': pack_id(ap[1]),
514                            'access' : [ { 'sshPubkey': x } for x in ssh ],
515                            }
516                        ]
517                    }
518                },
519            }
520        if len(self.attrs) > 0:
521            msg['emulab']['fedAttr'] = \
522                [ { 'attribute': x, 'value' : y } \
523                        for x,y in self.attrs.iteritems()]
524        return msg
525
526    def RequestAccess(self, req, fid):
527
528        if req.has_key('RequestAccessRequestBody'):
529            req = req['RequestAccessRequestBody']
530        else:
531            raise fedd_proj.service_error(service_error.req, "No request!?")
532
533        if req.has_key('destinationTestbed'):
534            dt = unpack_id(req['destinationTestbed'])
535       
536        if dt == None or dt == self.testbed:
537            # Request for this fedd
538            found, dyn = self.lookup_access(req, fid)
539
540            # Check for access to restricted nodes
541            if req.has_key('resources') and req['resources'].has_key('node'):
542                resources = req['resources']
543                inaccessible = [ t for n in resources['node'] \
544                                if n.has_key('hardware') != None \
545                                    for t in n['hardware'] \
546                                        if t in self.restricted and \
547                                                t not in found[0].node_types]
548                if len(inaccessible) > 0:
549                    raise fedd_proj.service_error(fedd_proj.service_error.access,
550                            "Access denied (nodetypes %s)" % \
551                            str(', ').join(inaccessible))
552
553            ssh = [ x['sshPubkey'] \
554                    for x in req['access'] if x.has_key('sshPubkey')]
555
556            if len(ssh) > 0: 
557                if dyn[1]: self.dynamic_project(found, ssh)
558                else: pass    # SSH key additions
559            else:
560                raise fedd_proj.service_error(service_error.req, 
561                        "SSH access parameters required")
562
563            resp = self.build_response(req['allocID'], found, ssh)
564            return resp
565        else:
566            p_fault = None      # Any SOAP failure (sent unless XMLRPC works)
567            try:
568                # Proxy the request using SOAP
569                return self.proxy_request(dt, req)
570            except fedd_proj.service_error, e:
571                if e.code == fedd_proj.service_error.proxy: p_fault = None
572                else: raise
573            except ZSI.FaultException, f:
574                p_fault = f.fault.detail[0]
575                   
576
577            # If we could not get a valid SOAP response to the request above,
578            # try the same address using XMLRPC and let any faults flow back
579            # out.
580            if p_fault == None:
581                return self.proxy_xmlrpc_request(dt, req)
582            else:
583                # Build the fault
584                body = p_fault.get_element_RequestAccessFaultBody()
585                if body != None:
586                    raise fedd_proj.service_error(body.get_element_code(),
587                                body.get_element_desc());
588                else:
589                    raise fedd_proj.service_error(\
590                            fedd_proj.service_error.proxy,
591                            "Undefined fault from proxy??");
592
593    def soap_RequestAccess(self, ps, fid):
594        req = ps.Parse(RequestAccessRequestMessage.typecode)
595
596        msg = self.RequestAccess(unpack_soap(req), fedid)
597       
598        resp = RequestAccessResponseMessage()
599        resp.set_element_RequestAccessResponseBody(
600                pack_soap(resp, "RequestAccessResponseBody", msg))
601
602        return resp
603
604    def xmlrpc_RequestAccess(self, params, fid):
605        msg = self.RequestAccess(params[0], fedid)
606
607        if msg != None:
608            return xmlrpclib.dumps(({ "RequestAccessResponseBody": msg },))
609        else:
610            raise service_error(fedd_proj.service_error.internal,
611                    "No response generated?!");
612
613    def read_trust(self, trust):
614        """
615        Read a trust file that splits fedids into testbeds, users or projects
616
617        Format is:
618
619        [type]
620        fedid
621        fedid
622        default: type
623        """
624        lineno = 0;
625        cat = None
626        cat_re = re.compile("\[(user|testbed|project)\]$", re.IGNORECASE)
627        fedid_re = re.compile("[" + string.hexdigits + "]+$")
628        default_re = re.compile("default:\s*(user|testbed|project)$", 
629                re.IGNORECASE)
630
631        f = open(trust, "r")
632        for line in f:
633            lineno += 1
634            line = line.strip()
635            if len(line) == 0 or line.startswith("#"):
636                continue
637            # Category line
638            m = cat_re.match(line)
639            if m != None:
640                cat = m.group(1).lower()
641                continue
642            # Fedid line
643            m = fedid_re.match(line)
644            if m != None:
645                if cat != None:
646                    self.fedid_category[fedid(hexstr=m.string)] = cat
647                else:
648                    raise fedd_proj.parse_error(\
649                            "Bad fedid in trust file (%s) line: %d" % \
650                            (trust, lineno))
651                continue
652            # default line
653            m = default_re.match(line)
654            if m != None:
655                self.fedid_default = m.group(1).lower()
656                continue
657            # Nothing matched - bad line, raise exception
658            f.close()
659            raise fedd_proj.parse_error(\
660                    "Unparsable line in trustfile %s line %d" % (trust, lineno))
661        f.close()
662
663    def read_config(self, config):
664        """
665        Read a configuration file and set internal parameters.
666
667        The format is more complex than one might hope.  The basic format is
668        attribute value pairs separated by colons(:) on a signle line.  The
669        attributes in bool_attrs, emulab_attrs and id_attrs can all be set
670        directly using the name: value syntax.  E.g.
671        boss: hostname
672        sets self.boss to hostname.  In addition, there are access lines of the
673        form (tb, proj, user) -> (aproj, auser) that map the first tuple of
674        names to the second for access purposes.  Names in the key (left side)
675        can include "<NONE> or <ANY>" to act as wildcards or to require the
676        fields to be empty.  Similarly aproj or auser can be <SAME> or
677        <DYNAMIC> indicating that either the matching key is to be used or a
678        dynamic user or project will be created.  These names can also be
679        federated IDs (fedid's) if prefixed with fedid:.  Finally, the aproj
680        can be followed with a colon-separated list of node types to which that
681        project has access (or will have access if dynamic).
682        Testbed attributes outside the forms above can be given using the
683        format attribute: name value: value.  The name is a single word and the
684        value continues to the end of the line.  Empty lines and lines startin
685        with a # are ignored.
686
687        Parsing errors result in a parse_error exception being raised.
688        """
689        lineno=0
690        name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+"
691        fedid_expr = "fedid:[" + string.hexdigits + "]+"
692        key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")"
693        access_proj = "(<DYNAMIC>(?::" + name_expr +")*|"+ \
694                "<SAME>" + "(?::" + name_expr + ")*|" + \
695                fedid_expr + "(?::" + name_expr + ")*|" + \
696                name_expr + "(?::" + name_expr + ")*)"
697        access_name = "(<DYNAMIC>|<SAME>|" + fedid_expr + "|"+ name_expr + ")"
698
699        bool_re = re.compile('(' + '|'.join(fedd_proj.bool_attrs) + 
700                '):\s+(true|false)', re.IGNORECASE)
701        string_re = re.compile( "(" + \
702                '|'.join(fedd_proj.emulab_attrs + fedd_proj.id_attrs) + \
703                '):\s*(.*)', re.IGNORECASE)
704        attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)',
705                re.IGNORECASE)
706        access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+
707                key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' + 
708                access_name + '\s*\)', re.IGNORECASE)
709        trustfile_re = re.compile("trustfile:\s*(.*)", re.IGNORECASE)
710        restricted_re = re.compile("restricted:\s*(.*)", re.IGNORECASE)
711
712        def parse_name(n):
713            if n.startswith('fedid:'): return fedid(n[len('fedid:'):])
714            else: return n
715
716        f = open(config, "r");
717        for line in f:
718            lineno += 1
719            line = line.strip();
720            if len(line) == 0 or line.startswith('#'):
721                continue
722
723            # Boolean attribute line
724            m = bool_re.match(line);
725            if m != None:
726                attr, val = m.group(1,2)
727                setattr(self, attr.lower(), bool(val.lower() == "true"))
728                continue
729
730            # String attribute line
731            m = string_re.match(line)
732            if m != None:
733                attr, val = m.group(1,2)
734                setattr(self, attr.lower(), val)
735                continue
736
737            # Extended (attribute: x value: y) attribute line
738            m = attr_re.match(line)
739            if m != None:
740                attr, val = m.group(1,2)
741                self.attrs[attr] = val
742                continue
743
744            # Access line (t, p, u) -> (ap, au) line
745            m = access_re.match(line)
746            if m != None:
747                access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
748                aps = m.group(4).split(":");
749                if aps[0] == 'fedid:':
750                    del aps[0]
751                    aps[0] = fedid(hexstr=aps[0])
752
753                au = m.group(5)
754                if au.startswith("fedid:"):
755                    au = fedid(hexstr=aus[len("fedid:"):])
756
757                access_val = (fedd_proj.access_project(aps[0], aps[1:]), au)
758
759                self.access[access_key] = access_val
760                continue
761
762            # Trustfile inclusion
763            m = trustfile_re.match(line)
764            if m != None:
765                self.read_trust(m.group(1))
766                continue
767            # Restricted node types
768
769            m = restricted_re.match(line)
770            if m != None:
771                self.restricted.append(m.group(1))
772                continue
773
774            # Nothing matched to here: unknown line - raise exception
775            f.close()
776            raise fedd_proj.parse_error("Unknown statement at line %d of %s" % \
777                    (lineno, config))
778        f.close()
779
780    def soap_dispatch(self, method, req, fid):
781        if fedd_proj.soap_methods.has_key(method):
782            try:
783                return getattr(self, fedd_proj.soap_methods[method])(req, fid)
784            except fedd_proj.service_error, e:
785                de = ns0.faultType_Def(
786                        (ns0.faultType_Def.schema,
787                            "RequestAccessFaultBody")).pyclass()
788                de._code=e.code
789                de._errstr=e.code_string()
790                de._desc=e.desc
791                if  e.is_server_error():
792                    raise Fault(Fault.Server, e.code_string(), detail=de)
793                else:
794                    raise Fault(Fault.Client, e.code_string(), detail=de)
795        else:
796            raise Fault(Fault.Client, "Unknown method: %s" % method)
797
798    def xmlrpc_dispatch(self, method, req, fid):
799        if fedd_proj.xmlrpc_methods.has_key(method):
800            try:
801                return getattr(self, fedd_proj.xmlrpc_methods[method])(req, fid)
802            except fedd_proj.service_error, e:
803                raise xmlrpclib.Fault(e.code_string(), e.desc)
804        else:
805            raise xmlrpclib.Fault(100, "Unknown method: %s" % method)
806
807def new_feddservice(configfile):
808    return fedd_proj(configfile)
Note: See TracBrowser for help on using the repository browser.