source: fedd/federation/access.py @ e087a7a

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

multiple uri aliases for the testbed

  • Property mode set to 100644
File size: 24.5 KB
Line 
1#!/usr/local/bin/python
2
3import os,sys
4import re
5import string
6import copy
7import pickle
8import logging
9
10from threading import *
11
12from util import *
13from allocate_project import allocate_project_local, allocate_project_remote
14from access_project import access_project
15from fedid import fedid, generate_fedid
16from authorizer import authorizer
17from service_error import service_error
18from remote_service import xmlrpc_handler, soap_handler, service_caller
19
20
21# Make log messages disappear if noone configures a fedd logger
22class nullHandler(logging.Handler):
23    def emit(self, record): pass
24
25fl = logging.getLogger("fedd.access")
26fl.addHandler(nullHandler())
27
28class access:
29    """
30    The implementation of access control based on mapping users to projects.
31
32    Users can be mapped to existing projects or have projects created
33    dynamically.  This implements both direct requests and proxies.
34    """
35
36    class parse_error(RuntimeError): pass
37
38    bool_attrs = ("project_priority", "allow_proxy")
39    emulab_attrs = ("boss", "ops", "domain", "fileserver", "eventserver")
40    id_attrs = ("proxy",
41            "proxy_cert_file", "proxy_cert_pwd", "proxy_trusted_certs",
42            "project_allocation_uri", "project_allocation_cert_file", 
43            "project_allocation_cert_pwd", "project_allocation_trusted_certs")
44    id_list_attrs = ("restricted",)
45
46    proxy_RequestAccess= service_caller('RequestAccess')
47    proxy_ReleaseAccess= service_caller('ReleaseAccess')
48
49    def __init__(self, config=None, auth=None):
50        """
51        Initializer.  Pulls parameters out of the ConfigParser's access section.
52        """
53
54        # Make sure that the configuration is in place
55        if config: 
56            if not config.has_section("access"):
57                config.add_section("access")
58            if not config.has_section("globals"):
59                config.add_section("globals")
60        else:
61            raise RunTimeError("No config to fedd.access")
62
63        # Create instance attributes from the static lists
64        for a in access.bool_attrs:
65            if config.has_option("access", a):
66                setattr(self, a, config.get("access", a))
67            else:
68                setattr(self, a, False)
69
70        for a in access.emulab_attrs + access.id_attrs:
71            if config.has_option("access", a):
72                setattr(self, a, config.get("access",a))
73            else:
74                setattr(self, a, None)
75
76        self.attrs = { }
77        self.access = { }
78        self.restricted = [ ]
79        self.fedid_category = { }
80        self.projects = { }
81        self.keys = { }
82        self.allocation = { }
83        self.state = { 
84            'projects': self.projects,
85            'allocation' : self.allocation,
86            'keys' : self.keys
87        }
88        self.log = logging.getLogger("fedd.access")
89        set_log_level(config, "access", self.log)
90        self.state_lock = Lock()
91
92        if auth: self.auth = auth
93        else:
94            self.log.error(\
95                    "[access]: No authorizer initialized, creating local one.")
96            auth = authorizer()
97
98        tb = config.get('access', 'testbed')
99        if tb: self.testbed = [ t.strip() for t in tb.split(',') ]
100        else: self.testbed = [ ]
101
102        if config.has_option("access", "accessdb"):
103            self.read_access(config.get("access", "accessdb"))
104
105        self.state_filename = config.get("access", "access_state", "")
106        self.read_state()
107        # Certs are promoted from the generic to the specific, so without a
108        # specific proxy certificate, the main certificates are used for
109        # proxy interactions. If no dynamic project certificates, then
110        # proxy certs are used, and if none of those the main certs.
111
112        if config.has_option("globals", "proxy_cert_file"):
113            if not self.project_allocation_cert_file:
114                self.project_allocation_cert_file = \
115                        config.get("globals", "proxy_cert_file")
116                if config.has_option("globals", "proxy_cert_pwd"):
117                    self.project_allocation_cert_pwd = \
118                            config.get("globals", "proxy_cert_pwd")
119
120        if config.has_option("globals", "proxy_trusted_certs"):
121            if not self.project_allocation_trusted_certs:
122                self.project_allocation_trusted_certs =\
123                        config.get("globals", proxy_trusted_certs)
124
125        if config.has_option("globals", "cert_file"):
126            has_pwd = config.has_option("globals", "cert_pwd")
127            if not self.project_allocation_cert_file:
128                self.project_allocation_cert_file = \
129                        config.get("globals", "cert_file")
130                if has_pwd: 
131                    self.project_allocation_cert_pwd = \
132                            config.get("globals", "cert_pwd")
133            if not self.proxy_cert_file:
134                self.proxy_cert_file = config.get("globals", "cert_file")
135                if has_pwd:
136                    self.proxy_cert_pwd = config.get("globals", "cert_pwd")
137
138        if config.get("globals", "trusted_certs"):
139            if not self.proxy_trusted_certs:
140                self.proxy_trusted_certs = \
141                        config.get("globals", "trusted_certs")
142            if not self.project_allocation_trusted_certs:
143                self.project_allocation_trusted_certs = \
144                        config.get("globals", "trusted_certs")
145
146        proj_certs = (self.project_allocation_cert_file, 
147                self.project_allocation_trusted_certs,
148                self.project_allocation_cert_pwd)
149
150        self.soap_services = {\
151            'RequestAccess': soap_handler("RequestAccess", self.RequestAccess),
152            'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess),
153            }
154        self.xmlrpc_services =  {\
155            'RequestAccess': xmlrpc_handler('RequestAccess',
156                self.RequestAccess),
157            'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
158                self.ReleaseAccess),
159            }
160
161
162        if not config.has_option("allocate", "uri"):
163            self.allocate_project = \
164                allocate_project_local(config, auth)
165        else:
166            self.allocate_project = \
167                allocate_project_remote(config, auth)
168
169        # If the project allocator exports services, put them in this object's
170        # maps so that classes that instantiate this can call the services.
171        self.soap_services.update(self.allocate_project.soap_services)
172        self.xmlrpc_services.update(self.allocate_project.xmlrpc_services)
173
174
175    def read_access(self, config):
176        """
177        Read a configuration file and set internal parameters.
178
179        The format is more complex than one might hope.  The basic format is
180        attribute value pairs separated by colons(:) on a signle line.  The
181        attributes in bool_attrs, emulab_attrs and id_attrs can all be set
182        directly using the name: value syntax.  E.g.
183        boss: hostname
184        sets self.boss to hostname.  In addition, there are access lines of the
185        form (tb, proj, user) -> (aproj, auser) that map the first tuple of
186        names to the second for access purposes.  Names in the key (left side)
187        can include "<NONE> or <ANY>" to act as wildcards or to require the
188        fields to be empty.  Similarly aproj or auser can be <SAME> or
189        <DYNAMIC> indicating that either the matching key is to be used or a
190        dynamic user or project will be created.  These names can also be
191        federated IDs (fedid's) if prefixed with fedid:.  Finally, the aproj
192        can be followed with a colon-separated list of node types to which that
193        project has access (or will have access if dynamic).
194        Testbed attributes outside the forms above can be given using the
195        format attribute: name value: value.  The name is a single word and the
196        value continues to the end of the line.  Empty lines and lines startin
197        with a # are ignored.
198
199        Parsing errors result in a self.parse_error exception being raised.
200        """
201        lineno=0
202        name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+"
203        fedid_expr = "fedid:[" + string.hexdigits + "]+"
204        key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")"
205        access_proj = "(<DYNAMIC>(?::" + name_expr +")*|"+ \
206                "<SAME>" + "(?::" + name_expr + ")*|" + \
207                fedid_expr + "(?::" + name_expr + ")*|" + \
208                name_expr + "(?::" + name_expr + ")*)"
209        access_name = "(<DYNAMIC>|<SAME>|" + fedid_expr + "|"+ name_expr + ")"
210
211        restricted_re = re.compile("restricted:\s*(.*)", re.IGNORECASE)
212        attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)',
213                re.IGNORECASE)
214        access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+
215                key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' + 
216                access_name + '\s*,\s*' + access_name + '\s*\)', re.IGNORECASE)
217
218        def parse_name(n):
219            if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):])
220            else: return n
221       
222        def auth_name(n):
223            if isinstance(n, basestring):
224                if n =='<any>' or n =='<none>': return None
225                else: return unicode(n)
226            else:
227                return n
228
229        f = open(config, "r");
230        for line in f:
231            lineno += 1
232            line = line.strip();
233            if len(line) == 0 or line.startswith('#'):
234                continue
235
236            # Extended (attribute: x value: y) attribute line
237            m = attr_re.match(line)
238            if m != None:
239                attr, val = m.group(1,2)
240                self.attrs[attr] = val
241                continue
242
243            # Restricted entry
244            m = restricted_re.match(line)
245            if m != None:
246                val = m.group(1)
247                self.restricted.append(val)
248                continue
249
250            # Access line (t, p, u) -> (ap, cu, su) line
251            m = access_re.match(line)
252            if m != None:
253                access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
254                auth_key = tuple([ auth_name(x) for x in access_key])
255                aps = m.group(4).split(":");
256                if aps[0] == 'fedid:':
257                    del aps[0]
258                    aps[0] = fedid(hexstr=aps[0])
259
260                cu = parse_name(m.group(5))
261                su = parse_name(m.group(6))
262
263                access_val = (access_project(aps[0], aps[1:]),
264                        parse_name(m.group(5)), parse_name(m.group(6)))
265
266                self.access[access_key] = access_val
267                self.auth.set_attribute(auth_key, "access")
268                continue
269
270            # Nothing matched to here: unknown line - raise exception
271            f.close()
272            raise self.parse_error("Unknown statement at line %d of %s" % \
273                    (lineno, config))
274        f.close()
275
276
277    def dump_state(self):
278        """
279        Dump the state read from a configuration file.  Mostly for debugging.
280        """
281        for a in access.bool_attrs:
282            print "%s: %s" % (a, getattr(self, a ))
283        for a in access.emulab_attrs + access.id_attrs:
284            print "%s: %s" % (a, getattr(self, a))
285        for k, v in self.attrs.iteritems():
286            print "%s %s" % (k, v)
287        print "Access DB:"
288        for k, v in self.access.iteritems():
289            print "%s %s" % (k, v)
290        print "Trust DB:"
291        for k, v in self.fedid_category.iteritems():
292            print "%s %s" % (k, v)
293        print "Restricted: %s" % str(',').join(sorted(self.restricted))
294
295    def get_users(self, obj):
296        """
297        Return a list of the IDs of the users in dict
298        """
299        if obj.has_key('user'):
300            return [ unpack_id(u['userID']) \
301                    for u in obj['user'] if u.has_key('userID') ]
302        else:
303            return None
304
305    def write_state(self):
306        try:
307            f = open(self.state_filename, 'w')
308            pickle.dump(self.state, f)
309        except IOError, e:
310            self.log.error("Can't write file %s: %s" % \
311                    (self.state_filename, e))
312        except pickle.PicklingError, e:
313            self.log.error("Pickling problem: %s" % e)
314        except TypeError, e:
315            self.log.error("Pickling problem (TypeError): %s" % e)
316
317
318    def read_state(self):
319        """
320        Read a new copy of access state.  Old state is overwritten.
321
322        State format is a simple pickling of the state dictionary.
323        """
324        try:
325            f = open(self.state_filename, "r")
326            self.state = pickle.load(f)
327
328            self.allocation = self.state['allocation']
329            self.projects = self.state['projects']
330            self.keys = self.state['keys']
331
332            self.log.debug("[read_state]: Read state from %s" % \
333                    self.state_filename)
334        except IOError, e:
335            self.log.warning("[read_state]: No saved state: Can't open %s: %s"\
336                    % (self.state_filename, e))
337        except EOFError, e:
338            self.log.warning("[read_state]: Empty or damaged state file: %s:"\
339                    % self.state_filename)
340        except pickle.UnpicklingError, e:
341            self.log.warning(("[read_state]: No saved state: " + \
342                    "Unpickling failed: %s") % e)
343
344        # Add the ownership attributes to the authorizer.  Note that the
345        # indices of the allocation dict are strings, but the attributes are
346        # fedids, so there is a conversion.
347        for k in self.allocation.keys():
348            for o in self.allocation[k].get('owners', []):
349                self.auth.set_attribute(o, fedid(hexstr=k))
350
351
352    def permute_wildcards(self, a, p):
353        """Return a copy of a with various fields wildcarded.
354
355        The bits of p control the wildcards.  A set bit is a wildcard
356        replacement with the lowest bit being user then project then testbed.
357        """
358        if p & 1: user = ["<any>"]
359        else: user = a[2]
360        if p & 2: proj = "<any>"
361        else: proj = a[1]
362        if p & 4: tb = "<any>"
363        else: tb = a[0]
364
365        return (tb, proj, user)
366
367    def find_access(self, search):
368        """
369        Search the access DB for a match on this tuple.  Return the matching
370        access tuple and the user that matched.
371       
372        NB, if the initial tuple fails to match we start inserting wildcards in
373        an order determined by self.project_priority.  Try the list of users in
374        order (when wildcarded, there's only one user in the list).
375        """
376        if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
377        else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
378
379        for p in perm: 
380            s = self.permute_wildcards(search, p)
381            # s[2] is None on an anonymous, unwildcarded request
382            if s[2] != None:
383                for u in s[2]:
384                    if self.access.has_key((s[0], s[1], u)):
385                        return (self.access[(s[0], s[1], u)], u)
386            else:
387                if self.access.has_key(s):
388                    return (self.access[s], None)
389        return None, None
390
391    def lookup_access(self, req, fid):
392        """
393        Determine the allowed access for this request.  Return the access and
394        which fields are dynamic.
395
396        The fedid is needed to construct the request
397        """
398        # Search keys
399        tb = None
400        project = None
401        user = None
402        # Return values
403        rp = access_project(None, ())
404        ru = None
405
406        if req.has_key('project'):
407            p = req['project']
408            if p.has_key('name'):
409                project = unpack_id(p['name'])
410            user = self.get_users(p)
411        else:
412            user = self.get_users(req)
413
414        user_fedids = [ u for u in user if isinstance(u, fedid)]
415        # Determine how the caller is representing itself.  If its fedid shows
416        # up as a project or a singleton user, let that stand.  If neither the
417        # usernames nor the project name is a fedid, the caller is a testbed.
418        if project and isinstance(project, fedid):
419            if project == fid:
420                # The caller is the project (which is already in the tuple
421                # passed in to the authorizer)
422                owners = user_fedids
423                owners.append(project)
424            else:
425                raise service_error(service_error.req,
426                        "Project asserting different fedid")
427        else:
428            if fid not in user_fedids:
429                tb = fid
430                owners = user_fedids
431                owners.append(fid)
432            else:
433                if len(fedids) > 1:
434                    raise service_error(service_error.req,
435                            "User asserting different fedid")
436                else:
437                    # Which is a singleton
438                    owners = user_fedids
439        # Confirm authorization
440        for u in user:
441            self.log.debug("[lookup_access] Checking access for %s" % \
442                    ((tb, project, u),))
443            if self.auth.check_attribute((tb, project, u), 'access'):
444                self.log.debug("[lookup_access] Access granted")
445                break
446            else:
447                self.log.debug("[lookup_access] Access Denied")
448        else:
449            raise service_error(service_error.access, "Access denied")
450
451        # This maps a valid user to the Emulab projects and users to use
452        found, user_match = self.find_access((tb, project, user))
453       
454        if found == None:
455            raise service_error(service_error.access,
456                    "Access denied - cannot map access")
457
458        # resolve <dynamic> and <same> in found
459        dyn_proj = False
460        dyn_create_user = False
461        dyn_service_user = False
462
463        if found[0].name == "<same>":
464            if project != None:
465                rp.name = project
466            else : 
467                raise service_error(\
468                        service_error.server_config,
469                        "Project matched <same> when no project given")
470        elif found[0].name == "<dynamic>":
471            rp.name = None
472            dyn_proj = True
473        else:
474            rp.name = found[0].name
475        rp.node_types = found[0].node_types;
476
477        if found[1] == "<same>":
478            if user_match == "<any>":
479                if user != None: rcu = user[0]
480                else: raise service_error(\
481                        service_error.server_config,
482                        "Matched <same> on anonymous request")
483            else:
484                rcu = user_match
485        elif found[1] == "<dynamic>":
486            rcu = None
487            dyn_create_user = True
488       
489        if found[2] == "<same>":
490            if user_match == "<any>":
491                if user != None: rsu = user[0]
492                else: raise service_error(\
493                        service_error.server_config,
494                        "Matched <same> on anonymous request")
495            else:
496                rsu = user_match
497        elif found[2] == "<dynamic>":
498            rsu = None
499            dyn_service_user = True
500
501        return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\
502                owners
503
504    def build_response(self, alloc_id, ap):
505        """
506        Create the SOAP response.
507
508        Build the dictionary description of the response and use
509        fedd_utils.pack_soap to create the soap message.  ap is the allocate
510        project message returned from a remote project allocation (even if that
511        allocation was done locally).
512        """
513        # Because alloc_id is already a fedd_services_types.IDType_Holder,
514        # there's no need to repack it
515        msg = { 
516                'allocID': alloc_id,
517                'emulab': { 
518                    'domain': self.domain,
519                    'boss': self.boss,
520                    'ops': self.ops,
521                    'fileServer': self.fileserver,
522                    'eventServer': self.eventserver,
523                    'project': ap['project']
524                },
525            }
526        if len(self.attrs) > 0:
527            msg['emulab']['fedAttr'] = \
528                [ { 'attribute': x, 'value' : y } \
529                        for x,y in self.attrs.iteritems()]
530        return msg
531
532    def RequestAccess(self, req, fid):
533        """
534        Handle the access request.  Proxy if not for us.
535
536        Parse out the fields and make the allocations or rejections if for us,
537        otherwise, assuming we're willing to proxy, proxy the request out.
538        """
539
540        # The dance to get into the request body
541        if req.has_key('RequestAccessRequestBody'):
542            req = req['RequestAccessRequestBody']
543        else:
544            raise service_error(service_error.req, "No request!?")
545
546        if req.has_key('destinationTestbed'):
547            dt = unpack_id(req['destinationTestbed'])
548
549        if dt == None or dt in self.testbed:
550            # Request for this fedd
551            found, dyn, owners = self.lookup_access(req, fid)
552            restricted = None
553            ap = None
554
555            # If this is a request to export a project and the access project
556            # is not the project to export, access denied.
557            if req.has_key('exportProject'):
558                ep = unpack_id(req['exportProject'])
559                if ep != found[0].name:
560                    raise service_error(service_error.access,
561                            "Cannot export %s" % ep)
562
563            # Check for access to restricted nodes
564            if req.has_key('resources') and req['resources'].has_key('node'):
565                resources = req['resources']
566                restricted = [ t for n in resources['node'] \
567                                if n.has_key('hardware') \
568                                    for t in n['hardware'] \
569                                        if t in self.restricted ]
570                inaccessible = [ t for t in restricted \
571                                    if t not in found[0].node_types]
572                if len(inaccessible) > 0:
573                    raise service_error(service_error.access,
574                            "Access denied (nodetypes %s)" % \
575                            str(', ').join(inaccessible))
576            # These collect the keys for the two roles into single sets, one
577            # for creation and one for service.  The sets are a simple way to
578            # eliminate duplicates
579            create_ssh = set([ x['sshPubkey'] \
580                    for x in req['createAccess'] \
581                        if x.has_key('sshPubkey')])
582
583            service_ssh = set([ x['sshPubkey'] \
584                    for x in req['serviceAccess'] \
585                        if x.has_key('sshPubkey')])
586
587            if len(create_ssh) > 0 and len(service_ssh) >0: 
588                if dyn[1]: 
589                    # Compose the dynamic project request
590                    # (only dynamic, dynamic currently allowed)
591                    preq = { 'AllocateProjectRequestBody': \
592                                { 'project' : {\
593                                    'user': [ \
594                                    { \
595                                        'access': [ { 'sshPubkey': s } \
596                                            for s in service_ssh ], 
597                                         'role': "serviceAccess",\
598                                    }, \
599                                    { \
600                                        'access': [ { 'sshPubkey': s } \
601                                            for s in create_ssh ], 
602                                         'role': "experimentCreation",\
603                                    }, \
604                                    ], \
605                                    }\
606                                }\
607                            }
608                    if restricted != None and len(restricted) > 0:
609                        preq['AllocateProjectRequestBody']['resources'] = \
610                            [ {'node': { 'hardware' :  [ h ] } } \
611                                    for h in restricted ]
612                    ap = self.allocate_project.dynamic_project(preq)
613                else:
614                    preq = {'StaticProjectRequestBody' : \
615                            { 'project': \
616                                { 'name' : { 'localname' : found[0].name },\
617                                  'user' : [ \
618                                    {\
619                                        'userID': { 'localname' : found[1] }, \
620                                        'access': [ { 'sshPubkey': s } 
621                                            for s in create_ssh ],
622                                        'role': 'experimentCreation'\
623                                    },\
624                                    {\
625                                        'userID': { 'localname' : found[2] }, \
626                                        'access': [ { 'sshPubkey': s } 
627                                            for s in service_ssh ],
628                                        'role': 'serviceAccess'\
629                                    },\
630                                ]}\
631                            }\
632                    }
633                    if restricted != None and len(restricted) > 0:
634                        preq['StaticProjectRequestBody']['resources'] = \
635                            [ {'node': { 'hardware' :  [ h ] } } \
636                                    for h in restricted ]
637                    ap = self.allocate_project.static_project(preq)
638            else:
639                raise service_error(service_error.req, 
640                        "SSH access parameters required")
641            # keep track of what's been added
642            allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
643            aid = unicode(allocID)
644
645            self.state_lock.acquire()
646            self.allocation[aid] = { }
647            if dyn[1]:
648                try:
649                    pname = ap['project']['name']['localname']
650                except KeyError:
651                    self.state_lock.release()
652                    raise service_error(service_error.internal,
653                            "Misformed allocation response?")
654                if self.projects.has_key(pname): self.projects[pname] += 1
655                else: self.projects[pname] = 1
656                self.allocation[aid]['project'] = pname
657
658            self.allocation[aid]['keys'] = [ ]
659
660            try:
661                for u in ap['project']['user']:
662                    uname = u['userID']['localname']
663                    for k in [ k['sshPubkey'] for k in u['access'] \
664                            if k.has_key('sshPubkey') ]:
665                        kv = "%s:%s" % (uname, k)
666                        if self.keys.has_key(kv): self.keys[kv] += 1
667                        else: self.keys[kv] = 1
668                        self.allocation[aid]['keys'].append((uname, k))
669            except KeyError:
670                self.state_lock.release()
671                raise service_error(service_error.internal,
672                        "Misformed allocation response?")
673
674            self.allocation[aid]['owners'] = owners
675            self.write_state()
676            self.state_lock.release()
677            for o in owners:
678                self.auth.set_attribute(o, allocID)
679            resp = self.build_response({ 'fedid': allocID } , ap)
680            return resp
681        else:
682            if self.allow_proxy:
683                resp = self.proxy_RequestAccess.call_service(dt, req,
684                            self.proxy_cert_file, self.proxy_cert_pwd,
685                            self.proxy_trusted_certs)
686                if resp.has_key('RequestAccessResponseBody'):
687                    return resp['RequestAccessResponseBody']
688                else:
689                    return None
690            else:
691                raise service_error(service_error.access,
692                        "Access proxying denied")
693
694    def ReleaseAccess(self, req, fid):
695        # The dance to get into the request body
696        if req.has_key('ReleaseAccessRequestBody'):
697            req = req['ReleaseAccessRequestBody']
698        else:
699            raise service_error(service_error.req, "No request!?")
700
701        if req.has_key('destinationTestbed'):
702            dt = unpack_id(req['destinationTestbed'])
703        else:
704            dt = None
705
706        if dt == None or dt in self.testbed:
707            # Local request
708            try:
709                if req['allocID'].has_key('localname'):
710                    auth_attr = aid = req['allocID']['localname']
711                elif req['allocID'].has_key('fedid'):
712                    aid = unicode(req['allocID']['fedid'])
713                    auth_attr = req['allocID']['fedid']
714                else:
715                    raise service_error(service_error.req,
716                            "Only localnames and fedids are understood")
717            except KeyError:
718                raise service_error(service_error.req, "Badly formed request")
719
720            if not self.auth.check_attribute(fid, auth_attr):
721                raise service_error(service_error.access, "Access Denied")
722
723            # If we know this allocation, reduce the reference counts and
724            # remove the local allocations.  Otherwise report an error.  If
725            # there is an allocation to delete, del_users will be a dictonary
726            # of sets where the key is the user that owns the keys in the set.
727            # We use a set to avoid duplicates.  del_project is just the name
728            # of any dynamic project to delete.  We're somewhat lazy about
729            # deleting authorization attributes.  Having access to something
730            # that doesn't exist isn't harmful.
731            del_users = { }
732            del_project = None
733            if self.allocation.has_key(aid):
734                self.state_lock.acquire()
735                for k in self.allocation[aid]['keys']:
736                    kk = "%s:%s" % k
737                    self.keys[kk] -= 1
738                    if self.keys[kk] == 0:
739                        if not del_users.has_key(k[0]):
740                            del_users[k[0]] = set()
741                        del_users[k[0]].add(k[1])
742                        del self.keys[kk]
743
744                if self.allocation[aid].has_key('project'):
745                    pname = self.allocation[aid]['project']
746                    self.projects[pname] -= 1
747                    if self.projects[pname] == 0:
748                        del_project = pname
749                        del self.projects[pname]
750
751                del self.allocation[aid]
752                self.write_state()
753                self.state_lock.release()
754                # If we actually have resources to deallocate, prepare the call.
755                if del_project or del_users:
756                    msg = { 'project': { }}
757                    if del_project:
758                        msg['project']['name']= {'localname': del_project}
759                    users = [ ]
760                    for u in del_users.keys():
761                        users.append({ 'userID': { 'localname': u },\
762                            'access' :  \
763                                    [ {'sshPubkey' : s } for s in del_users[u]]\
764                        })
765                    if users: 
766                        msg['project']['user'] = users
767                    if self.allocate_project.release_project:
768                        msg = { 'ReleaseProjectRequestBody' : msg}
769                        self.allocate_project.release_project(msg)
770                return { 'allocID': req['allocID'] } 
771            else:
772                raise service_error(service_error.req, "No such allocation")
773
774        else:
775            if self.allow_proxy:
776                resp = self.proxy_ReleaseAccess.call_service(dt, req,
777                            self.proxy_cert_file, self.proxy_cert_pwd,
778                            self.proxy_trusted_certs)
779                if resp.has_key('ReleaseAccessResponseBody'):
780                    return resp['ReleaseAccessResponseBody']
781                else:
782                    return None
783            else:
784                raise service_error(service_error.access,
785                        "Access proxying denied")
786
787
Note: See TracBrowser for help on using the repository browser.