source: fedd/fedd/access.py @ 6a0c9f4

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

More namespace cleanup

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