source: fedd/fedd_access.py @ 5576a47

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

project exporting in place

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