source: fedd/fedd_access.py @ 3f6bc5f

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

Initial move to general authorization framework. Currently integrated with Access stuff fully.

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