source: fedd/federation/access.py @ 1b376ca

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

Correct resource communication to allocation and wildcarding of gateway requests

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