source: fedd/fedd_access.py @ a94cb0a

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

missed a comment

  • Property mode set to 100644
File size: 23.7 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        # Add the ownership attributes to the authorizer.  Note that the
352        # indices of the allocation dict are strings, but the attributes are
353        # fedids, so there is a conversion.
354        for k in self.allocation.keys():
355            for o in self.allocation[k].get('owners', []):
356                self.auth.set_attribute(o, fedid(hexstr=k))
357
358
359    def permute_wildcards(self, a, p):
360        """Return a copy of a with various fields wildcarded.
361
362        The bits of p control the wildcards.  A set bit is a wildcard
363        replacement with the lowest bit being user then project then testbed.
364        """
365        if p & 1: user = ["<any>"]
366        else: user = a[2]
367        if p & 2: proj = "<any>"
368        else: proj = a[1]
369        if p & 4: tb = "<any>"
370        else: tb = a[0]
371
372        return (tb, proj, user)
373
374    def find_access(self, search):
375        """
376        Search the access DB for a match on this tuple.  Return the matching
377        access tuple and the user that matched.
378       
379        NB, if the initial tuple fails to match we start inserting wildcards in
380        an order determined by self.project_priority.  Try the list of users in
381        order (when wildcarded, there's only one user in the list).
382        """
383        if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
384        else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
385
386        for p in perm: 
387            s = self.permute_wildcards(search, p)
388            # s[2] is None on an anonymous, unwildcarded request
389            if s[2] != None:
390                for u in s[2]:
391                    if self.access.has_key((s[0], s[1], u)):
392                        return (self.access[(s[0], s[1], u)], u)
393            else:
394                if self.access.has_key(s):
395                    return (self.access[s], None)
396        return None, None
397
398    def lookup_access(self, req, fid):
399        """
400        Determine the allowed access for this request.  Return the access and
401        which fields are dynamic.
402
403        The fedid is needed to construct the request
404        """
405        # Search keys
406        tb = None
407        project = None
408        user = None
409        # Return values
410        rp = access_project(None, ())
411        ru = None
412
413        if req.has_key('project'):
414            p = req['project']
415            if p.has_key('name'):
416                project = unpack_id(p['name'])
417            user = self.get_users(p)
418        else:
419            user = self.get_users(req)
420
421        user_fedids = [ u for u in user if isinstance(u, fedid)]
422        # Determine how the caller is representing itself.  If its fedid shows
423        # up as a project or a singleton user, let that stand.  If neither the
424        # usernames nor the project name is a fedid, the caller is a testbed.
425        if project and isinstance(project, fedid):
426            if project == fid:
427                # The caller is the project (which is already in the tuple
428                # passed in to the authorizer)
429                owners = user_fedids
430                owners.append(project)
431            else:
432                raise service_error(service_error.req,
433                        "Project asserting different fedid")
434        else:
435            if fid not in user_fedids:
436                tb = fid
437                owners = user_fedids
438                owners.append(fid)
439            else:
440                if len(fedids) > 1:
441                    raise service_error(service_error.req,
442                            "User asserting different fedid")
443                else:
444                    # Which is a singleton
445                    owners = user_fedids
446        # Confirm authorization
447        for u in user:
448            if self.auth.check_attribute((tb, project, u), 'access'):
449                print "Access OK"
450                break
451        else:
452            print "Access failed"
453            raise service_error(service_error.access, "Access denied")
454
455        # This maps a valid user to the Emulab projects and users to use
456        found, user_match = self.find_access((tb, project, user))
457       
458        if found == None:
459            raise service_error(service_error.access,
460                    "Access denied - cannot map access")
461
462        # resolve <dynamic> and <same> in found
463        dyn_proj = False
464        dyn_create_user = False
465        dyn_service_user = False
466
467        if found[0].name == "<same>":
468            if project != None:
469                rp.name = project
470            else : 
471                raise service_error(\
472                        service_error.server_config,
473                        "Project matched <same> when no project given")
474        elif found[0].name == "<dynamic>":
475            rp.name = None
476            dyn_proj = True
477        else:
478            rp.name = found[0].name
479        rp.node_types = found[0].node_types;
480
481        if found[1] == "<same>":
482            if user_match == "<any>":
483                if user != None: rcu = user[0]
484                else: raise service_error(\
485                        service_error.server_config,
486                        "Matched <same> on anonymous request")
487            else:
488                rcu = user_match
489        elif found[1] == "<dynamic>":
490            rcu = None
491            dyn_create_user = True
492       
493        if found[2] == "<same>":
494            if user_match == "<any>":
495                if user != None: rsu = user[0]
496                else: raise service_error(\
497                        service_error.server_config,
498                        "Matched <same> on anonymous request")
499            else:
500                rsu = user_match
501        elif found[2] == "<dynamic>":
502            rsu = None
503            dyn_service_user = True
504
505        return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\
506                owners
507
508    def build_response(self, alloc_id, ap):
509        """
510        Create the SOAP response.
511
512        Build the dictionary description of the response and use
513        fedd_utils.pack_soap to create the soap message.  NB that alloc_id is
514        a fedd_services_types.IDType_Holder pulled from the incoming message.
515        ap is the allocate project message returned from a remote project
516        allocation (even if that allocation was done locally).
517        """
518        # Because alloc_id is already a fedd_services_types.IDType_Holder,
519        # there's no need to repack it
520        msg = { 
521                'allocID': alloc_id,
522                'emulab': { 
523                    'domain': self.domain,
524                    'boss': self.boss,
525                    'ops': self.ops,
526                    'fileServer': self.fileserver,
527                    'eventServer': self.eventserver,
528                    'project': ap['project']
529                },
530            }
531        if len(self.attrs) > 0:
532            msg['emulab']['fedAttr'] = \
533                [ { 'attribute': x, 'value' : y } \
534                        for x,y in self.attrs.iteritems()]
535        return msg
536
537    def RequestAccess(self, req, fid):
538        """
539        Handle the access request.  Proxy if not for us.
540
541        Parse out the fields and make the allocations or rejections if for us,
542        otherwise, assuming we're willing to proxy, proxy the request out.
543        """
544
545        # The dance to get into the request body
546        if req.has_key('RequestAccessRequestBody'):
547            req = req['RequestAccessRequestBody']
548        else:
549            raise service_error(service_error.req, "No request!?")
550
551        if req.has_key('destinationTestbed'):
552            dt = unpack_id(req['destinationTestbed'])
553
554        if dt == None or dt == self.testbed:
555            # Request for this fedd
556            found, dyn, owners = self.lookup_access(req, fid)
557            restricted = None
558            ap = None
559
560            # Check for access to restricted nodes
561            if req.has_key('resources') and req['resources'].has_key('node'):
562                resources = req['resources']
563                restricted = [ t for n in resources['node'] \
564                                if n.has_key('hardware') \
565                                    for t in n['hardware'] \
566                                        if t in self.restricted ]
567                inaccessible = [ t for t in restricted \
568                                    if t not in found[0].node_types]
569                if len(inaccessible) > 0:
570                    raise service_error(service_error.access,
571                            "Access denied (nodetypes %s)" % \
572                            str(', ').join(inaccessible))
573            # These collect the keys for teh two roles into single sets, one
574            # for creation and one for service.  The sets are a simple way to
575            # eliminate duplicates
576            create_ssh = set([ x['sshPubkey'] \
577                    for x in req['createAccess'] \
578                        if x.has_key('sshPubkey')])
579
580            service_ssh = set([ x['sshPubkey'] \
581                    for x in req['serviceAccess'] \
582                        if x.has_key('sshPubkey')])
583
584            if len(create_ssh) > 0 and len(service_ssh) >0: 
585                if dyn[1]: 
586                    # Compose the dynamic project request
587                    # (only dynamic, dynamic currently allowed)
588                    preq = { 'AllocateProjectRequestBody': \
589                                { 'project' : {\
590                                    'user': [ \
591                                    { \
592                                        'access': [ { 'sshPubkey': s } \
593                                            for s in service_ssh ], 
594                                         'role': "serviceAccess",\
595                                    }, \
596                                    { \
597                                        'access': [ { 'sshPubkey': s } \
598                                            for s in create_ssh ], 
599                                         'role': "experimentCreation",\
600                                    }, \
601                                    ], \
602                                    }\
603                                }\
604                            }
605                    if restricted != None and len(restricted) > 0:
606                        preq['AllocateProjectRequestBody']['resources'] = \
607                            [ {'node': { 'hardware' :  [ h ] } } \
608                                    for h in restricted ]
609                               
610                    ap = self.allocate_project.dynamic_project(preq)
611                else:
612                    preq = {'StaticProjectRequestBody' : \
613                            { 'project': \
614                                { 'name' : { 'localname' : found[0].name },\
615                                  'user' : [ \
616                                    {\
617                                        'userID': { 'localname' : found[1] }, \
618                                        'access': [ { 'sshPubkey': s } 
619                                            for s in create_ssh ],
620                                        'role': 'experimentCreation'\
621                                    },\
622                                    {\
623                                        'userID': { 'localname' : found[2] }, \
624                                        'access': [ { 'sshPubkey': s } 
625                                            for s in service_ssh ],
626                                        'role': 'serviceAccess'\
627                                    },\
628                                ]}\
629                            }\
630                    }
631                    if restricted != None and len(restricted) > 0:
632                        preq['StaticProjectRequestBody']['resources'] = \
633                            [ {'node': { 'hardware' :  [ h ] } } \
634                                    for h in restricted ]
635                    ap = self.allocate_project.static_project(preq)
636            else:
637                raise service_error(service_error.req, 
638                        "SSH access parameters required")
639            # keep track of what's been added
640            allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
641            aid = unicode(allocID)
642
643            self.state_lock.acquire()
644            self.allocation[aid] = { }
645            if dyn[1]:
646                try:
647                    pname = ap['project']['name']['localname']
648                except KeyError:
649                    self.state_lock.release()
650                    raise service_error(service_error.internal,
651                            "Misformed allocation response?")
652                if self.projects.has_key(pname): self.projects[pname] += 1
653                else: self.projects[pname] = 1
654                self.allocation[aid]['project'] = pname
655
656            self.allocation[aid]['keys'] = [ ]
657
658            try:
659                for u in ap['project']['user']:
660                    uname = u['userID']['localname']
661                    for k in [ k['sshPubkey'] for k in u['access'] \
662                            if k.has_key('sshPubkey') ]:
663                        kv = "%s:%s" % (uname, k)
664                        if self.keys.has_key(kv): self.keys[kv] += 1
665                        else: self.keys[kv] = 1
666                        self.allocation[aid]['keys'].append((uname, k))
667            except KeyError:
668                self.state_lock.release()
669                raise service_error(service_error.internal,
670                        "Misformed allocation response?")
671
672            self.allocation[aid]['owners'] = owners
673            self.write_state()
674            self.state_lock.release()
675            for o in owners:
676                self.auth.set_attribute(o, allocID)
677            resp = self.build_response({ 'fedid': allocID } , ap)
678            return resp
679        else:
680            resp = self.proxy_RequestAccess.call_service(dt, req,
681                        self.proxy_cert_file, self.proxy_cert_pwd,
682                        self.proxy_trusted_certs)
683            if resp.has_key('RequestAccessResponseBody'):
684                return resp['RequestAccessResponseBody']
685            else:
686                return None
687
688    def ReleaseAccess(self, req, fid):
689        # The dance to get into the request body
690        if req.has_key('ReleaseAccessRequestBody'):
691            req = req['ReleaseAccessRequestBody']
692        else:
693            raise service_error(service_error.req, "No request!?")
694
695        try:
696            if req['allocID'].has_key('localname'):
697                auth_attr = aid = req['allocID']['localname']
698            elif req['allocID'].has_key('fedid'):
699                aid = unicode(req['allocID']['fedid'])
700                auth_attr = req['allocID']['fedid']
701            else:
702                raise service_error(service_error.req,
703                        "Only localnames and fedids are understood")
704        except KeyError:
705            raise service_error(service_error.req, "Badly formed request")
706
707        print "Checking for %s %s" % (fid, auth_attr)
708        if not self.auth.check_attribute(fid, auth_attr):
709            raise service_error(service_error.access, "Access Denied")
710
711        # If we know this allocation, reduce the reference counts and remove
712        # the local allocations.  Otherwise report an error.  If there is an
713        # allocation to delete, del_users will be a dictonary of sets where the
714        # key is the user that owns the keys in the set.  We use a set to avoid
715        # duplicates.  del_project is just the name of any dynamic project to
716        # delete.
717        # We're somewhat lazy about deleting authorization attributes.  Having
718        # access to something that doesn't exist isn't harmful.
719        del_users = { }
720        del_project = None
721        if self.allocation.has_key(aid):
722            self.state_lock.acquire()
723            for k in self.allocation[aid]['keys']:
724                kk = "%s:%s" % k
725                self.keys[kk] -= 1
726                if self.keys[kk] == 0:
727                    if not del_users.has_key(k[0]):
728                        del_users[k[0]] = set()
729                    del_users[k[0]].add(k[1])
730                    del self.keys[kk]
731
732            if self.allocation[aid].has_key('project'):
733                pname = self.allocation[aid]['project']
734                self.projects[pname] -= 1
735                if self.projects[pname] == 0:
736                    del_project = pname
737                    del self.projects[pname]
738
739            del self.allocation[aid]
740            self.write_state()
741            self.state_lock.release()
742            # If we actually have resources to deallocate, prepare the call.
743            if del_project or del_users:
744                msg = { 'project': { }}
745                if del_project:
746                    msg['project']['name']= {'localname': del_project}
747                users = [ ]
748                for u in del_users.keys():
749                    users.append({ 'userID': { 'localname': u },\
750                        'access' :  \
751                                [ {'sshPubkey' : s } for s in del_users[u]]\
752                    })
753                if users: 
754                    msg['project']['user'] = users
755                if self.allocate_project.release_project:
756                    msg = { 'ReleaseProjectRequestBody' : msg}
757                    self.allocate_project.release_project(msg)
758            return { 'allocID': req['allocID'] } 
759        else:
760            raise service_error(service_error.req, "No such allocation")
761
762
763
Note: See TracBrowser for help on using the repository browser.