source: fedd/fedd_access.py @ 058f58e

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

Unify the code for calling SOAP and XMLRPC services into a couple classes.
Before there were slightly different semantics everywhere.

Also make the handlers classes rather than the output of stub compiling
functions.

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