source: fedd/fedd_access.py @ 51cc9df

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

split fedid out

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