source: fedd/fedd_access.py @ 9460b1e

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

move remote_service out of fedd_util

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