source: fedd/federation/access.py @ c3dcf48

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

Snapshot of new state-based allocation

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