source: fedd/fedd_access.py @ c35207d

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

proxy release (some in prevoius commit)

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